一、线程状态

1、线程的六种状态

java.lang.Thread.State
1、New:尚未启动的线程的线程状态
2、Runnable:可运行线程的线程状态,等待CPU调度
3、Blocked:线程阻塞等待监视器锁定的线程状态。
处于synchronized同步代码块或方法中被阻塞。
4、Waiting:等待线程的线程状态。下列不带超时的方式:
Object.wait、Thread.join、LockSupport.park
5、Timed Waiting:具有指定等待时间的等待线程的线程状态。下列带超时的方式:
Thread.sleep、Object.wait、Thread.join、LockSupport.parkNanos、LockSupport.parkUntil
6、Terminated:终止线程的线程状态。线程正常完成执行或者出现异常。

2、线程状态直接的切换

image.png

二、线程终止

正确的线程终止——interrupt

如果目标线程在调用Object class的wait()、wait(long)或wait(long, int)方法、join()、join(long, int)或sleep(long, int)方法时被阻塞,那么Interrupt会生效,该线程的中断状态将被清除,抛出InterruptedException异常。
如果目标线程是被I/O或者NIO中的channel所阻塞,同样,I/O操作会被中断或者返回特殊异常值。达到终止线程的目的。
如果以上条件都不满足,则会设置此线程的中断状态。

正确的线程终止——标志位

在代码逻辑中,增加一个判断,用来控制线程的终止。

三、线程通信

通信的方式

想要实现多个线程之间的协同,如:线程执行先后顺序、获取某个线程执行的结果等等。
涉及到线程之间的相互通信,分为下面四类:
1、文件共享
2、网络共享
3、共享变量
4、jdk提供的线程协调API

1、文件共享

image.png

image.png

2、网络共享

3、变量共享

image.png
image.png

4、线程协作 - JDK API ★

JDK中对于需要多线程写作完成某一任务的场景,提供了对应API支持。
多线程协作的典型场景是:生产者-消费者模型。(线程阻塞、线程唤醒)
示例:线程1去买包子,没有包子,则不再执行。线程2生产出包子,通知线程1继续执行。
image.png

(1)被弃用的suspend和resume

作用:调用suspend挂起目标线程,通过resume可以恢复线程执行。
用法:

  1. //包子店
  2. public static Object baozidian = null;
  3. public void suspendResumeTest() throws InterruptedException {
  4. //启动线程
  5. Thread consumerThread = new Thread(()-> {
  6. if(baozidian == null) {
  7. System.out.println("1、没包子,进入等待");
  8. Thread.currentThread().suspend();
  9. }
  10. System.out.println("2、买到包子,回家");
  11. });
  12. consumerThread.start();
  13. //3秒后,生成一个包子
  14. Thread.sleep(3000);
  15. baozidian = new Object();
  16. consumerThread.resume();
  17. System.out.println("3、通知消费者");
  18. }

被弃用的主要原因是,suspend()方法调用后,不会自动释放锁,容易写出死锁的代码。
所以用wait/notify和park/unpark机制对它进行替换

suspend和resume死锁示例
1)同步代码中使用

  1. public void suspendResumeDeadLockTest() throws InterruptedException {
  2. Thread consumerThread = new Thread(() -> {
  3. if(baozidian == null) {
  4. System.out.println("1、没包子,进入等待");
  5. synchronized (this) {
  6. //当前线程拿到锁,然后挂起
  7. Thread.currentThread().suspend();
  8. }
  9. }
  10. System.out.println("2、买到包子,回家");
  11. });
  12. consumerThread.start();
  13. Thread.sleep(3000);
  14. baozidian = new Object();
  15. synchronized (this) {
  16. consumerThread.resume();
  17. }
  18. System.out.println("3、通知消费者");
  19. }

2)suspend比resume后执行

  1. public void suspendResumeDeadLockTest2() throws InterruptedException {
  2. Thread consumerThread = new Thread(() -> {
  3. if(baozidian == null) {
  4. System.out.println("1、没包子,进入等待");
  5. try { //为这个线程加上一点延时
  6. Thread.sleep(5000);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. //这里挂起的执行在resume后面
  11. Thread.currentThread().suspend();
  12. }
  13. System.out.println("2、买到包子,回家");
  14. });
  15. consumerThread.start();
  16. //3秒后,生成一个包子
  17. Thread.sleep(3000);
  18. baozidian = new Object();
  19. consumerThread.resume();
  20. System.out.println("3、通知消费者");
  21. }

(2)wait/notify机制

这些方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出IllegalMonitorStateException异常。
wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。

注意:虽然wait会自动解锁,但是对顺序有要求,如果在notify被调用之后,才开始wait方法的调用,线程会永远处于WAITING状态。

用法:

  1. public void waitNotifyTest() throws InterruptedException {
  2. new Thread(() -> {
  3. if(baozidian == null) {
  4. synchronized (this) {
  5. System.out.println("1、没包子,进入等待");
  6. try {
  7. this.wait();
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. }
  13. System.out.println("2、买到包子,回家");
  14. }).start();
  15. //3秒之后,生产一个包子
  16. Thread.sleep(3000);
  17. baozidian = new Object();
  18. synchronized (this) {
  19. this.notifyAll();
  20. System.out.println("3、通知消费者");
  21. }
  22. }

死锁示例:

  1. public void waitNotifyDeadLockTest() throws InterruptedException {
  2. new Thread(() -> {
  3. if(baozidian == null) {
  4. try {
  5. Thread.sleep(5000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. synchronized (this) {
  10. System.out.println("1、没包子,进入等待");
  11. try {
  12. this.wait();
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }
  18. System.out.println("2、买到包子,回家");
  19. }).start();
  20. //3秒之后,生产一个包子
  21. Thread.sleep(3000);
  22. baozidian = new Object();
  23. synchronized (this) {
  24. this.notifyAll();
  25. System.out.println("3、通知消费者");
  26. }
  27. }

(3)park/unpark机制

线程调用park则等待“许可”,unpark方法为指定线程提供“许可(permit)”。

不要求park和unpark方法的调用顺序。
**
多次调用unpark之后,再调用park,线程会直接运行,但不会叠加,也就是说,连续多次调用park方法,第一次会拿到“许可”直接运行,后续调用会进入等待。

用法:

  1. public void parkUnparkTest() throws InterruptedException {
  2. Thread consumerThread = new Thread(() -> {
  3. if(baozidian == null) {
  4. System.out.println("1、没包子,进入等待");
  5. LockSupport.park();
  6. }
  7. System.out.println("2、买到包子,回家");
  8. });
  9. consumerThread.start();
  10. //3秒之后,生产一个包子
  11. Thread.sleep(3000);
  12. baozidian = new Object();
  13. LockSupport.unpark(consumerThread);
  14. System.out.println("3、通知消费者");
  15. }

注意:同步代码容易写出死锁代码
死锁示例:

  1. public void parkUnparkDeadLockTest() throws InterruptedException {
  2. Thread consumerThread = new Thread(() -> {
  3. if(baozidian == null) {
  4. synchronized (this) {
  5. System.out.println("1、没包子,进入等待");
  6. LockSupport.park();
  7. }
  8. }
  9. System.out.println("2、买到包子,回家");
  10. });
  11. consumerThread.start();
  12. //3秒之后,生产一个包子
  13. Thread.sleep(3000);
  14. baozidian = new Object();
  15. synchronized (this) {
  16. LockSupport.unpark(consumerThread);
  17. System.out.println("3、通知消费者");
  18. }
  19. }

伪唤醒

警告!之前代码中用if语句来判断是否进入等待状态是错误的!

官方建议
应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误警报和伪唤醒**,如果不在循环中检查等待条件,程序就会再没有满足结束条件的情况下退出。

伪唤醒是指线程并非因为notify、notifyAll、unpark等api调用而唤醒,是更底层原因导致的。

四、线程池

1、为什么要用线程池

image.png

2、线程池原理

image.png

3、线程池API

image.png
image.png
image.png