1. 线程创建与运行
1.1 线程创建的四种方式
方法1. 使用Thread
@Slf4j(topic = "c.CreateThread1")public class CreateThread1 {public static void main(String[] args) {Thread t1 = new Thread("t1"){public void run(){log.debug("t1-running");}};t1.start();log.debug("main-running");}}
13:40:33.366 [main] DEBUG c.CreateThread1 - main-running13:40:33.366 [t1] DEBUG c.CreateThread1 - t1-running
方法2.使用 Runnable 配合 Thread(推荐)
把【线程】和【任务】(要执行的代码)分开,Thread 代表线程,Runnable 可运行的任务(线程要执行的代码)
@Slf4j(topic = "c.CreateThread2")public class CreateThread2 {public static void main(String[] args) {// 任务对象Runnable runnable = new Runnable() {@Overridepublic void run() {log.debug("running");}};// 线程对象Thread thread = new Thread(runnable, "t2");// 启动thread.start();}}
13:43:42.104 [t2] DEBUG c.CreateThread2 - running
小结
- 方法1 是把线程和任务合并在了一起,
- 方法2 是把线程和任务分开了(推荐)
- 用 Runnable 更容易与线程池等高级 API 配合,用 Runnable 让任务类脱离了 Thread 继承体系,更灵活。通过查看源码可以发现,方法二其实到底还是通过方法一执行的!
源码:
- 创建线程为Thread类的init方法的重载,target对象为自定义的Runnable对象,

在执行Thread的run()方法时,他会判断是否target对象,如果有,就用。
方法3 FutureTask 配合 Thread
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况。FutureTask 同时实现了Runnable, Future接口(FutureTask实现了RunnableFuture接口,该接口继承Runnable接口和Future接口)。Future接口可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。FutrueTask是Futrue接口的唯一的实现类。 它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值。
Thread类中没有一个方法是执行Callable的,所以需要外界帮助。通过适配器模式将Callable和Runnable接口建立联系。在FutureTask类中通过构造方法将实现了Callable接口的类放入。即对象适配器思想。一方面,FutureTask可以当做Runnable被Thread执行,另一方面实现了Future接口,可以用Future接口的get方法获取到Callable接口中call方法的计算结果)
- Callable接口:与Runnable相比,功能更强
- 相比run方法,可以有返回值
- 方法可以抛出异常,被外面的操作捕获,获取异常信息
- 支持泛型的返回值
- 需要借助FutureTask类(是Future接口的唯一实现类,可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等)
future接口与Callable接口的关系
- 可以用Future.get()来获取Callable接口返回的执行任务,
- Futuren.isDone()来判断任务是否已经执行完成或者取消任务。
- 如果call()方法还没执行完,调用get()方法的线程会被阻塞,直到call()方法返回结果,主线程才会恢复。
可以认为Future是一个存储器,存储了call()这个任务的结果。
@Slf4j(topic = "c.CreateThread3")public class CreateThread3 {public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建FutureTask对象(作为线程要实现的内容),将实现了Callable接口的对象传入,FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {log.debug("running");Thread.sleep(1000);return 100;}});// 将task对象传入t1线程中Thread t1 = new Thread(task, "t1");t1.start();// 等待task返回结果Integer integer = task.get();log.debug("{}", integer);}}
14:24:25.084 [t1] DEBUG c.CreateThread3 - running14:24:26.092 [main] DEBUG c.CreateThread3 - 100
1.2 查看进程线程的方法
windows
- 任务管理器可以查看进程和线程数,也可以用来杀死进程
- tasklist 查看进程
- taskkill 杀死进程
- linux
- ps -fe 查看所有进程
- ps -fT -p
查看某个进程(PID)的所有线程 - kill 杀死进程
- top 按大写 H 切换是否显示线程
- top -H -p
查看某个进程(PID)的所有线程
Java
每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存
-
1.3.2 线程上下文切换(Thread Context Switch)
为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
线程的 cpu 时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法
当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念 就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
1.4 Thread的常见方法
1.4.1 start() vs run()
1. start
public static void main(String[] args) {Thread thread = new Thread(){@Overridepublic void run(){log.debug("我是一个新建的线程正在运行中");FileReader.read(fileName);}};thread.setName("新建线程");thread.start();log.debug("主线程");}
输出:程序在 t1 线程运行,run()方法里面内容的调用是异步的
11:59:40.711 [main] DEBUG com.concurrent.test.Test4 - 主线程11:59:40.711 [新建线程] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中11:59:40.732 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] start ...11:59:40.735 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 3 ms
2. run
将上面代码的thread.start();改为thread.run();输出结果如下:程序仍在 main 线程运行,run()方法里面内容的调用还是同步的
12:03:46.711 [main] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中12:03:46.727 [main] DEBUG com.concurrent.test.FileReader - read [test] start ...12:03:46.729 [main] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 2 ms12:03:46.730 [main] DEBUG com.concurrent.test.Test4 - 主线程
3.小结
直接调用run()是在主线程中执行了run(),没有启动新的线程;使用start()是启动新的线程,通过新的线程间接执行run()方法中的代码
start方法对线程状态的影响
@Slf4j(topic = "c.test1")public class ThreadTest1 {public static void main(String[] args) {Thread t1 = new Thread("t1"){public void run(){log.debug("running...");}};System.out.println(t1.getState());t1.start();System.out.println(t1.getState());}}
NEWRUNNABLE15:29:00.095 [t1] DEBUG c.test1 - running...
1.4.2 sleep() vs yield()
1. sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,那么被打断的线程这时就会抛出InterruptedException异常【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】
- 睡眠结束后的线程未必会立刻得到执行(需要分配到cpu时间片)
- 建议用 TimeUnit 的sleep()代替 Thread 的sleep()来获得更好的可读性
sleep的使用: Timed Waiting
@Slf4j(topic = "c.ThreadTest2")public class ThreadTest2 {public static void main(String[] args) {Thread t1 = new Thread("t1"){@Overridepublic void run() {try {// 让t1线程进入休眠状态Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}};t1.start();// 打印t1状态,由于主线程创建之后,t1线程才创建出来,当主线程执行该方法时,t1线程可能还没有休眠log.debug("t1 state:{}" , t1.getState()); // RUNNABLE// 解决办法,给主线程休眠一下,try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}log.debug("t1 state:{}" , t1.getState()); // TIMED_WAITING}}
15:39:04.488 [main] DEBUG c.ThreadTest2 - t1 state:RUNNABLE15:39:04.997 [main] DEBUG c.ThreadTest2 - t1 state:TIMED_WAITING
sleep的使用:打断睡眠
@Slf4j(topic = "c.ThreadTest3")public class ThreadTest3 {public static void main(String[] args) {Thread t1 = new Thread("t1"){@Overridepublic void run() {log.debug("t1 enter sleep...");try {Thread.sleep(2000);} catch (InterruptedException e) {log.debug("wake up");e.printStackTrace();}}};t1.start();try {Thread.sleep(1000);// 打断log.debug("interrupt");t1.interrupt();} catch (InterruptedException e) {e.printStackTrace();}}}
15:52:31.713 [t1] DEBUG c.ThreadTest3 - t1 enter sleep...15:52:32.716 [main] DEBUG c.ThreadTest3 - interrupt15:52:32.717 [t1] DEBUG c.ThreadTest3 - wake upjava.lang.InterruptedException: sleep interruptedat java.lang.Thread.sleep(Native Method)at com.ll.ch2.ThreadTest3$1.run(ThreadTest3.java:14)
TimeUnit
@Slf4j(topic = "c.ThreadTest4")public class ThreadTest4 {public static void main(String[] args) {try {log.debug("enter");TimeUnit.SECONDS.sleep(1);log.debug("end");} catch (InterruptedException e) {e.printStackTrace();}}}
15:55:27.245 [main] DEBUG c.ThreadTest4 - enter15:55:28.255 [main] DEBUG c.ThreadTest4 - end
2. yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器(就是可能没有其它的线程正在执行,虽然调用了yield方法想把cpu的使用权让出去,但是还是执行该线程)
3.小结
yield(Runnable状态)使cpu调用其它线程,但是cpu可能会再分配时间片给该线程;而sleep(Time waiting状态)需要等过了休眠时间之后才有可能被分配cpu时间片4. sleep的应用
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或sleep来让出cpu的使用权给其他程序。可以使用wait或条件变量达到类似效果;不同的是,后两种需要加锁,适用于同步场景;sleep适用于无需锁同步的场景。1.4.3 线程优先级
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它。
- 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用。
设置优先级的源码
public final void setPriority(int newPriority) {ThreadGroup g;checkAccess();if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {throw new IllegalArgumentException();}if((g = getThreadGroup()) != null) {if (newPriority > g.getMaxPriority()) {newPriority = g.getMaxPriority();}setPriority0(priority = newPriority);}}
不使用yield
@Slf4j(topic = "c.ThreadTest5")public class ThreadTest5 {public static void main(String[] args) {Runnable task1 = new Runnable() {@Overridepublic void run() {int cnt = 0;for (;;){System.out.println("--->1 " + cnt++);}}};Runnable task2 = new Runnable() {@Overridepublic void run() {int cnt = 0;for (;;){System.out.println(" --->2 " + cnt++);}}};Thread t1 = new Thread(task1, "t1");Thread t2 = new Thread(task2, "t2");t1.start();t2.start();}}

t1与t2势均力敌
使用yield
@Slf4j(topic = "c.ThreadTest5")public class ThreadTest5 {public static void main(String[] args) {Runnable task1 = new Runnable() {@Overridepublic void run() {int cnt = 0;for (;;){System.out.println("--->1 " + cnt++);}}};Runnable task2 = new Runnable() {@Overridepublic void run() {int cnt = 0;for (;;){Thread.yield();System.out.println(" --->2 " + cnt++);}}};Thread t1 = new Thread(task1, "t1");Thread t2 = new Thread(task2, "t2");t1.start();t2.start();}}

可以看到t1明显比t2的数大,t2让步了。
加入优先级
@Slf4j(topic = "c.ThreadTest5")public class ThreadTest5 {public static void main(String[] args) {Runnable task1 = new Runnable() {@Overridepublic void run() {int cnt = 0;for (;;){System.out.println("--->1 " + cnt++);}}};Runnable task2 = new Runnable() {@Overridepublic void run() {int cnt = 0;for (;;){//Thread.yield();System.out.println(" --->2 " + cnt++);}}};Thread t1 = new Thread(task1, "t1");Thread t2 = new Thread(task2, "t2");t1.setPriority(Thread.MIN_PRIORITY);t2.setPriority(Thread.MAX_PRIORITY);t1.start();t2.start();}}
1.4.4 join的使用
例子1:
@Slf4j(topic = "c.JoinTest1")public class JoinTest1 {private static int r = 0;public static void main(String[] args) throws InterruptedException {log.debug("开始");Thread t1 = new Thread(() -> {try {log.debug("开始");Thread.sleep(1000);log.debug("结束");r = 10;} catch (InterruptedException e) {e.printStackTrace();}}, "t1");t1.start();// t1.join();log.debug("结果为:{}", r);log.debug("结束");}}
18:03:36.497 [main] DEBUG c.JoinTest1 - 开始18:03:36.567 [t1] DEBUG c.JoinTest1 - 开始18:03:36.567 [main] DEBUG c.JoinTest1 - 结果为:018:03:36.570 [main] DEBUG c.JoinTest1 - 结束18:03:37.569 [t1] DEBUG c.JoinTest1 - 结束
从结果中可以看到:r的值为0。因为t1的赋值在sleep方法之后进行的,但是主线程执行时,t1线程也开始执行了,主线程打印的时候t1线程还在sleep中,没有进行赋值。
解决办法
- sleep时间不好把控
使用join方法,等待线程运行结束 ```java @Slf4j(topic = “c.JoinTest1”) public class JoinTest1 { private static int r = 0;
public static void main(String[] args) throws InterruptedException {
log.debug("开始");Thread t1 = new Thread(() -> {try {log.debug("开始");Thread.sleep(1000);log.debug("结束");r = 10;} catch (InterruptedException e) {e.printStackTrace();}}, "t1");t1.start();t1.join();log.debug("结果为:{}", r);log.debug("结束");
} }
```java18:03:08.987 [main] DEBUG c.JoinTest1 - 开始18:03:09.057 [t1] DEBUG c.JoinTest1 - 开始18:03:10.062 [t1] DEBUG c.JoinTest1 - 结束18:03:10.063 [main] DEBUG c.JoinTest1 - 结果为:1018:03:10.064 [main] DEBUG c.JoinTest1 - 结束
主线程等待t1结束才运行,得到了赋值的结果10
线程的同步
同步:需要等待结果返回,才能继续运行;
异步:不需要等待结果返回,就能继续运行。
例子2
@Slf4j( topic = "c.JoinTest2")public class JoinTest2 {private static int r1 = 0;private static int r2 = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {sleep(1000);r1 = 10;} catch (InterruptedException e) {e.printStackTrace();}});Thread t2 = new Thread(() -> {try {sleep(2000);r2 = 20;} catch (InterruptedException e) {e.printStackTrace();}});t1.start();t2.start();long start = System.currentTimeMillis();log.debug("join begin");t1.join();log.debug("t1 join end");t2.join();log.debug("t2 join end");long end = System.currentTimeMillis();log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);}}
18:10:13.041 [main] DEBUG c.JoinTest2 - join begin18:10:14.044 [main] DEBUG c.JoinTest2 - t1 join end18:10:15.044 [main] DEBUG c.JoinTest2 - t2 join end18:10:15.044 [main] DEBUG c.JoinTest2 - r1: 10 r2: 20 cost: 2005
从结果可以看出,等待时间为2秒钟,因为开始的时候主线程和t1、t2线程同时开始,所以只需等待2秒钟即可,不用等待3秒钟。(t2相较于t1晚1秒打印)
交换两个join
@Slf4j( topic = "c.JoinTest2")public class JoinTest2 {private static int r1 = 0;private static int r2 = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {sleep(1000);r1 = 10;} catch (InterruptedException e) {e.printStackTrace();}});Thread t2 = new Thread(() -> {try {sleep(2000);r2 = 20;} catch (InterruptedException e) {e.printStackTrace();}});t1.start();t2.start();long start = System.currentTimeMillis();log.debug("join begin");t2.join();log.debug("t2 join end");t1.join();log.debug("t1 join end");long end = System.currentTimeMillis();log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);}
18:18:02.773 [main] DEBUG c.JoinTest2 - join begin18:18:04.775 [main] DEBUG c.JoinTest2 - t2 join end18:18:04.775 [main] DEBUG c.JoinTest2 - t1 join end18:18:04.776 [main] DEBUG c.JoinTest2 - r1: 10 r2: 20 cost: 2005
先对t2线程join,到t1线程join时,已经不需要等了,因为t1线程早已经结束了(t1与t2的打印时间相同)
限时同步
@Slf4j(topic = "c.JoinTest3")public class JoinTest3 {private static int r1 = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {sleep(2000);r1 = 10;} catch (InterruptedException e) {e.printStackTrace();}});long start = System.currentTimeMillis();t1.start();// 线程执行结束会导致 join 结束log.debug("join begin");// 设置join等待1500ms就不再等了t1.join(1500);long end = System.currentTimeMillis();log.debug("r1: {} cost: {}", r1, end - start);}}
18:30:12.519 [main] DEBUG c.JoinTest3 - join begin18:30:14.025 [main] DEBUG c.JoinTest3 - r1: 0 cost: 1508
因为设置了join的等待时间,主线程等待1500毫秒就不再等了,而t1线程会在2000毫秒赋值,所以r1值为0
@Slf4j(topic = "c.JoinTest3")public class JoinTest3 {private static int r1 = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {Thread.sleep(2000);r1 = 10;} catch (InterruptedException e) {e.printStackTrace();}});long start = System.currentTimeMillis();t1.start();// 线程执行结束会导致 join 结束log.debug("join begin");// 设置join等待3000ms就不再等了t1.join(3000);long end = System.currentTimeMillis();log.debug("r1: {} cost: {}", r1, end - start);}}
18:31:41.227 [main] DEBUG c.JoinTest3 - join begin18:31:43.228 [main] DEBUG c.JoinTest3 - r1: 10 cost: 2003
将等待时间设置为3000毫秒时,能获取到正常赋值,同时在t1结束的时候主线程也就结束了。
1.4.5 interrupt 方法详解
1.打断 sleep,wait,join 的线程
sleep,wait,join 的线程,这几个方法都会让线程进入阻塞状态,interrupt用于打断阻塞状态,同时会清除打断标记
以 sleep 为例
@Slf4j(topic = "InterruptTest1")public class InterruptTest1 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread("t1"){@Overridepublic void run() {log.debug("sleep...");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}};t1.start();Thread.sleep(500);log.debug("interrupt");t1.interrupt();log.debug("打断标记:{}", t1.isInterrupted());}}
18:41:22.355 [t1] DEBUG InterruptTest1 - sleep...18:41:22.856 [main] DEBUG InterruptTest1 - interrupt18:41:22.857 [main] DEBUG InterruptTest1 - 打断标记:falsejava.lang.InterruptedException: sleep interruptedat java.lang.Thread.sleep(Native Method)at com.ll.ch2.InterruptTest1$1.run(InterruptTest1.java:13)
2. 打断正常运行的线程
打断正常运行的线程, 线程并不会暂停,只是调用方法Thread.currentThread().isInterrupted();的返回值为true,可以判断Thread.currentThread().isInterrupted();的值来手动停止线程
@Slf4j(topic = "InterruptTest2")public class InterruptTest2 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread("t1"){@Overridepublic void run() {while (true){boolean flag = Thread.currentThread().isInterrupted();if (isInterrupted()){log.debug("被打断了,退出循环");break;}}}};t1.start();Thread.sleep(1000);log.debug("打断t1");t1.interrupt();}}
11:42:52.312 [main] DEBUG InterruptTest2 - 打断t111:42:52.316 [t1] DEBUG InterruptTest2 - 被打断了,退出循环
两阶段终止模式
- 使用线程对象的stop方法停止线程
- stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程将永远无法获取锁。
- 使用System.exit(int) 方法停止线程
- 目的仅是停止一个线程,这种做法会让整个程序停止。
Two Phase Termination,就是考虑在一个线程T1中如何优雅地终止另一个线程T2?这里的优雅指的是给T2一个料理后事的机会(如释放锁),监控线程在监控过程中停止监控了就可以采用这个方式。
如下所示:线程的isInterrupted()方法可以取得线程的打断标记,如果线程在睡眠sleep期间被打断,打断标记是不会变的,为false,但是sleep期间被打断会抛出异常,我们据此手动设置打断标记为true;如果是在程序正常运行期间被打断的,那么打断标记就被自动设置为true。处理好这两种情况那我们就可以放心地来料理后事啦!
@Slf4j(topic = "c.TerminationModeTest")public class TerminationModeTest {public static void main(String[] args) throws InterruptedException {TwoPhaseTermination t = new TwoPhaseTermination();// 启动监控线程t.start();// 主线程等3.5秒后Thread.sleep(3500);// 优雅的停止监控线程t.stop();}}@Slf4j(topic = "c.TwoPhaseTermination")class TwoPhaseTermination {private Thread monitor;public void start(){monitor = new Thread("monitor"){@Overridepublic void run() {while (true){// 拿到当前线程Thread current = Thread.currentThread();// 判断是否被打断if (current.isInterrupted()){// 被打断log.debug("处理后事");break;}try {Thread.sleep(1000);log.debug("监控记录");} catch (InterruptedException e) {// 睡眠期间被打断e.printStackTrace();// 重新设置打断标记current.interrupt();}}}};monitor.start();}public void stop(){monitor.interrupt();}}
11:34:43.983 [monitor] DEBUG c.TwoPhaseTermination - 监控记录11:34:44.990 [monitor] DEBUG c.TwoPhaseTermination - 监控记录11:34:45.995 [monitor] DEBUG c.TwoPhaseTermination - 监控记录java.lang.InterruptedException: sleep interruptedat java.lang.Thread.sleep(Native Method)at com.ll.ch2.TwoPhaseTermination$1.run(TerminationModeTest.java:34)11:34:46.483 [monitor] DEBUG c.TwoPhaseTermination - 处理后事
3.打断park线程
park线程让当前线程停下来
@Slf4j(topic = "ParkTest")public class ParkTest {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()-> {log.debug("park....");LockSupport.park();log.debug("unpark.......");log.debug("打断状态{}", Thread.currentThread().isInterrupted());LockSupport.park();log.debug("unpark....");}, "t1");t1.start();Thread.sleep(1000);t1.interrupt();}}
11:49:54.913 [t1] DEBUG ParkTest - park....11:49:55.916 [t1] DEBUG ParkTest - unpark.......11:49:55.917 [t1] DEBUG ParkTest - 打断状态true11:49:55.919 [t1] DEBUG ParkTest - unpark....
park()将t1线程挂起,主线程睡1秒后打断t1线程的park状态,此时t1线程继续执行,注意park一旦被打断就无法继续挂起线程。
4.isInterrupted() 与 interrupted() 比较
首先,isInterrupted 是实例方法,interrupted 是静态方法,它们的用处都是查看当前打断的状态,但是 isInterrupted 方法查看线程的时候,不会将打断标记清空,也就是置为 false,interrupted 查看线程打断状态后,会将打断标志置为 false,也就是清空打断标记,简单来说,interrupt() 方法类似于 setter 设置中断值,isInterrupted() 类似于 getter 获取中断值,interrupted() 类似于 getter + setter 先获取中断值,然后清除标志。
1.4.6 过时的方法
stop():停止线程运行
suspend():挂起(暂停)线程运行
resume():恢复线程运行
这些方法已过时,会破坏同步代码块,造成线程死锁。
1.4.7 守护线程
默认情况下,java进程需要等待所有的线程结束后才会停止,但是有一种特殊的线程,叫做守护线程,在其他线程全部结束的时候即使守护线程还未结束代码未执行完java进程也会停止。普通线程t1可以调用t1.setDeamon(true);方法变成守护线程。
代码示例:
@Slf4j(topic = "DaemonTest")public class DaemonTest {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{while (true){if (Thread.currentThread().isInterrupted()){break;}}log.debug("t1结束");}, "t1");//t1.setDaemon(true);t1.start();Thread.sleep(1000);log.debug("主线程结束");}}

可以看到主线程结束了,但是t1线程还没有结束,所以java进程不会 停止,还在运行中
守护线程
设置t1为守护线程
@Slf4j(topic = "DaemonTest")public class DaemonTest {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{while (true){if (Thread.currentThread().isInterrupted()){break;}}log.debug("t1结束");}, "t1");t1.setDaemon(true);t1.start();Thread.sleep(1000);log.debug("主线程结束");}}

可以随着主线程结束,进程就终止了。守护线程t1不管结没结束,也就随着终止了。
注意
- 垃圾回收器线程就是一种守护线程。进程结束了,垃圾回收也就停止。
Tomcat 中的 Acceptor(接收请求) 和 Poller (分发请求)线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
2.线程状态
2.1 线程的5种状态
从操作系统层面划分,线程有5种状态

初始状态,仅仅是在语言层面上创建了线程对象,即Thead thread = new Thead();,还未与操作系统线程关联
- 可运行状态,也称就绪状态,指该线程已经被创建,与操作系统相关联,等待cpu给它分配时间片就可运行
- 运行状态,指线程获取了CPU时间片,正在运行
- 当CPU时间片用完,线程会转换至【可运行状态】,等待 CPU再次分配时间片,会导致我们前面讲到的上下文切换
- 阻塞状态
- 如果调用了阻塞API,如BIO读写文件,那么线程实际上不会用到CPU,不会分配CPU时间片,会导致上下文切换,进入【阻塞状态】
- 等待BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,只要操作系统一直不唤醒线程,调度器就一直不会考虑调度它们,CPU就一直不会分配时间片
终止状态,表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
2.2 线程的6种状态
这是从 Java API 层面来描述的

1.初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 2.运行(RUNNABLE):Java线程中将就绪、运行、阻塞(IO阻塞,java无法区分)三种状态笼统的称为“运行”。
- 阻塞(BLOCKED):表示线程阻塞于锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
- 终止(TERMINATED):表示该线程已经执行完毕。
代码示例
@Slf4j(topic = "c.StateTest")public class StateTest {public static void main(String[] args) throws IOException {Thread t1 = new Thread("t1") {@Overridepublic void run() {log.debug("running...");}};Thread t2 = new Thread("t2") {@Overridepublic void run() {while(true) { // runnable}}};t2.start();Thread t3 = new Thread("t3") {@Overridepublic void run() {log.debug("running...");}};t3.start();Thread t4 = new Thread("t4") {@Overridepublic void run() {synchronized (StateTest.class) {try {Thread.sleep(1000000); // timed_waiting} catch (InterruptedException e) {e.printStackTrace();}}}};t4.start();Thread t5 = new Thread("t5") {@Overridepublic void run() {try {t2.join(); // waiting} catch (InterruptedException e) {e.printStackTrace();}}};t5.start();Thread t6 = new Thread("t6") {@Overridepublic void run() {synchronized (StateTest.class) { // blockedtry {Thread.sleep(1000000);} catch (InterruptedException e) {e.printStackTrace();}}}};t6.start();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}log.debug("t1 state {}", t1.getState());log.debug("t2 state {}", t2.getState());log.debug("t3 state {}", t3.getState());log.debug("t4 state {}", t4.getState());log.debug("t5 state {}", t5.getState());log.debug("t6 state {}", t6.getState());System.in.read();}}
13:42:08.467 [t3] DEBUG c.StateTest - running...13:42:08.970 [main] DEBUG c.StateTest - t1 state NEW13:42:08.973 [main] DEBUG c.StateTest - t2 state RUNNABLE13:42:08.973 [main] DEBUG c.StateTest - t3 state TERMINATED13:42:08.973 [main] DEBUG c.StateTest - t4 state TIMED_WAITING13:42:08.973 [main] DEBUG c.StateTest - t5 state WAITING13:42:08.973 [main] DEBUG c.StateTest - t6 state BLOCKED
t1是新建的一个线程,还没有开始执行;t2是正在执行的线程,由于有个死循环,所以他会一直执行;t3就执行了一次,由于主线睡了0.5秒,这个时候t3已经执行完了;t4睡的时间比主线程长;t5在等待t2执行完才能执行,且没有时间限制;t4线程加了锁,t6也对同一个加锁,等待t4释放锁。
3.结论
本章的重点在于掌握
1)线程的创建
2)线程重要的 API,如 start、run、sleep、yield、join、interrupt 等
3)线程的状态
4)原理方面,线程的运行流程,栈、栈帧、上下文切换、程序计数器等知识。
5)Thread 两种创建线程的源码
6)使用 interrupt 来编写两阶段终止

