线程是任务调度和执行的基本单位,可以看做是轻量级的进程,多线程是指在同一程序中有多个顺序流在执行,也就是一个进程中同时执行多个线程,两个或两个以上的线程对同一个变量的操作.如果两个线程修改同一个对象的状态,根据线程访问数据的次序,可能会产生错误的数据,也就常说的并发问题.
线程的基本概念与创建
在学线程之前,首先来来了解一下线程与进程的区别:
进程:
- 是系统资源分配的基本单位
- 每个进程都有独立的代码和数据空间,程序切换会有较大的开销
- 在操作系统中可以同时运行多个程序
- 在运行时会为每个进程分配不同的内存空间
线程:
- 是任务调度和执行的基本单位
- 可以看做是轻量级的进程,同一类的线程共享代码和数据空间,线程之间切换开销较小
- 多个线程可以运行在同一进程中
- 除了CPU,系统不会为线程分配内存,线程组之间只能共享数据
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止
创建的线程有四种方式:
- 继承Thread
- 通过实现Runnable接口
- 通过实现Callable接口
- 通过线程池创建
1.继承Thread类
class TestThread extends Thread{
private volatile int ticket = 10;
@Override
public void run() {
synchronized (this){
while (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票操作 ticket = "+ticket--);
}
}
}
}
public class Test{
public static void main(String[] args) throws Exception{
TestThread testThread = new TestThread();
testThread.run();
}
}
运行结果:

2.实现Runnable接口
经典的生产者和消费者
class Product implements Runnable{//生产者
private Message message;
Product(Message message) {
this.message = message;
}
public void run() {
for (int x = 0 ; x < 100 ; x++){
if (x % 2 == 0){
this.message.set("生产者","生产");
}else {
this.message.set("消费者","消费");
}
}
}
}
----
class Consumer implements Runnable{//消费者
private Message message;
public Consumer(Message message) {
this.message = message;
}
public void run() {
for (int x = 0 ; x < 100 ; x++){
System.out.println(this.message.get());
}
}
}
----
class Message{ //消息中心
private String title;
private String content;
private boolean flag = true;//表示生产或者消费的形式
// flag = true 允许生产,不允许消费 flag= false 允许消费,不允许生产
synchronized void set(String title, String content){
if (!flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.title = title;
this.content = content;
this.flag = false; //生产完成
notify();//唤醒等待的线程
}
synchronized String get(){
if (flag == true){
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
return this.title + " ------------ "+this.content;
}finally {
this.flag = true; // 可继续生产
notify();//唤醒等待线程
}
}
}
----
public class Producer {
public static void main(String[] args) throws Exception{
Message message = new Message();
new Thread(new Product(message),"生产者").start();//启动生产者线程
new Thread(new Consumer(message),"消费者").start();//启动消费者线程
}
}
运行结果

3.实现Callable接口
class TheThread implements Callable<String>{
private boolean flag = false;
@Override
public String call() throws Exception {
synchronized (this){
if (this.flag==false){
this.flag = true;
return Thread.currentThread().getName()+"抢答成功";
}else {
return Thread.currentThread().getName()+"抢答失败";
}
}
}
}
public class Competition {
public static void main(String[] args) throws ExecutionException, InterruptedException {
TheThread theThread = new TheThread();
FutureTask<String> futureTaskA = new FutureTask<>(theThread);
FutureTask<String> futureTaskB = new FutureTask<>(theThread);
FutureTask<String> futureTaskC = new FutureTask<>(theThread);
new Thread(futureTaskA,"竞赛者A").start();
new Thread(futureTaskB,"竞赛者B").start();
new Thread(futureTaskC,"竞赛者C").start();
System.out.println(futureTaskA.get());
System.out.println(futureTaskB.get());
System.out.println(futureTaskC.get());
}
}
运行结果

4.使用线程池
class ExecutorTest{
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor service = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
new ThreadFactoryBuilder().setNameFormat("test-thread-pool-%d").build(),
new ThreadPoolExecutor.AbortPolicy());
Runnable runnable = () -> {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("线程1执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Runnable runnable1 = () -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("线程2执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
int num = 10;
for (int i = 0; i < num; i++) {
service.execute(runnable);
service.execute(runnable1);
}
}
}
运行结果

以上案例都是处理过的线程,都是通过使用了synchronized锁保证了原子性,也就是线程安全的操作,线程的安全的实现方式,除了使用锁之外,也可以用CAS实现无锁安全操作.
CAS无锁实现线程安全操作
示例:
class CAS{
private AtomicInteger ticket = new AtomicInteger(10);
public void run() {
while (ticket.get()>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票操作 ticket = "+ticket.getAndDecrement());
}
}
}
----
public class CASTest{
public static void main(String[] args) throws Exception{
CAS cas = new CAS();
cas.run();
}
}
程序执行结果

以上是使用了atomic包下线程安全的AtomicInteger,下面是atomic包下的线程安全类以及部分源码

Atomiclnteger本身是个整型,最重要的属性是value,而value使用了volatile关键字,value的作用就是保证可见性,即一个线程修改了共享变量时,其他线程读取的是修改后的新值.
getAndIncrement()方法实现了自增操作.核心实现是先获取当前值和目标值如果compareAndSet(current,next)成功返回该方法的目标值.getAndDecrement()方法则实现了自减操作.
AtomicInteger中的CAS操作就是compareAndSet(),作用是每次从内存中根据内存偏移量(valueOffset)取出数据,将取出的值跟expect比较,如果数据一致就把内存中的值改为update.这样就保证了原子操作.
AtomicInteger主要实现了整型的原子操作,防止并发的情况下出现异常结果,内部主要依靠JDK中的unsafe类操作内存中的数据实现.volatile修饰符保证了可见性,CAS操作保证了原子操作
volatile关键字
若某一属性使用volatile关键字,则表示不操作副本,直接操作原始变量.
volatile关键字的两层含义:
一旦一个共享变量(类的成员变量,类的静态成员变量)被volatile修饰之后,那么久具体两层语义:
- 保证了不同线程对这个变量进行操作的可见性,即一个线程修改了某个共享变量的值,这个新值对其他线程来说是立即可见的.
- 禁止进行指令重排序
禁止指令重排序有两层意思:
- 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
- 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
volatile关键字需要结合锁来使用
示例:
class MyThread implements Runnable{
private volatile int ticket = 10;
@Override
public void run() {
synchronized (this){
while (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票操作 ticket = "+ticket--);
}
}
}
}
public class Volatile{
public static void main(String[] args) throws Exception{
MyThread myThread = new MyThread();
new Thread(myThread,"票贩子A").start();
new Thread(myThread,"票贩子B").start();
new Thread(myThread,"票贩子C").start();
}
}
运行结果

本案例与上面的继承Thread类的案例是同一个.
在并发编程中会遇到三个问题:原子性、可见性、有序性
- 可见性:
volatile关键字在这里的作用是当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值.保证了可见性.普通的共享变量不能保证可见性,因为普通变量被修改之后,什么时候写入主存是不确定的,当其他线程去读的时候,内存中可能还是原来的旧值,因此无法保证可见性.通过synchronized和Lock也能够保证可见性,synchronized和lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存中,因此可以保证其可见性.
- 原子性:
java的内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和lock来实现.由于synchronized和lock能够保证一个时刻只有一个线程执行该代码块,那么自然不存在原子性问题,从而保证了原子性.
- 有序性:
java的内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程的执行,却会影响到多线程并发执行的正确性.
可以通过volatile关键字来保证一定的"有序性",也可以通过synchronized和lock来保证有序性,synchronized和lock保证每一刻只有一个线程执行同步代码,相当于让线程顺序执行同步代码,自然就保证了有序性.
java内存模型具备一些先天的"有序性",即不需要通过任何手段就能够保证有序性,这个通常也称为happens-before原则(先行发生原则).如果两个操作的执行次序无法从happens-before原则推导出来,那么他们就不能保证它们的有序性,虚拟机可以随意的对它们进行重排序.
happens-before原则(先行发生原则):
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
- 锁定规则:一个unLock操作先行发生于后面对同一个锁lock操作
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返
回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
这8条原则摘自《深入理解Java虚拟机》。
守护线程
public class Daemon {
public static void main(String[] args) throws Exception{
Thread userThread = new Thread(() -> {
for (int x = 0 ; x < 10 ; x++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行 x = " + x);
}
},"执行线程");
Thread daemonThread = new Thread(() -> {
for (int x = 0 ; x < Integer.MAX_VALUE ; x++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行 x = " + x);
}
},"守护线程");
daemonThread.setDaemon(true);//将daemonThread设为守护线程
userThread.start();
daemonThread.start();
}
}
运行结果

在Thread类提供了以下的守护线程的操作方法,可自行查看源码
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
public final boolean isDaemon() {
return daemon;
}
停止线程
public class StopThread {
public static boolean flag = true;
public static void main(String[] args) throws Exception{
new Thread(() -> {
long num = 0;
while (flag){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行 num = " + num++);
}
},"执行线程").start();
Thread.sleep(200);//线程运行200毫秒
flag = false;//停止线程
}
}
运行结果

多线程操作中使用的启动线程的方法是Thread类的start()方法,原本也提供有stop()方法来停止线程,但在1.2之后已经废除,现在停止线程的方法可使用上面案例所使用方式.
线程优先级
public static void main(String[] args) {
Runnable runnable = () -> {
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("线程1执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable runnable1 = () -> {
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("线程2执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
Thread thread1 = new Thread(runnable1);
thread.setPriority(1);
thread1.setPriority(10);
thread.start();
thread1.start();
运行结果

在Java程序设计中,每一个线程都有一个优先级,默认情况下,一个线程继承父线程的优先级,也可以通过设置setPriority方法来改变线程的优先级,优先级设置的范围是在MIN_PRIORITY(在Thread类中定义为1),MAX_PRIORITY(定义为10)和NORM_PRIORITY(定义为5),这三个优先级的值在Threa类中是三个静态常量,如下:
- public static final int MIN_PRIORITY = 1;
- public static final int NORM_PRIORITY = 5;
- public static final int MAX_PRIORITY = 10;
上面线程优先级的示例及运行结果说明,线程的优先级在优先级最高时并不一定会先抢占到资源,线程的优先级是高度依赖于系统的,例如Windows 有7个优先级别。一些Java优先级将映射到相同的操作系统优先级。在Oracle为Linux提供的Java虚拟机中线程的优先级被忽略一所有线程具有相同的优先级.
以上是线程的基本概念以及基础操作,若有不足或错误,欢迎指出.