wait原理

  • entry set :入口集
  • wait set :等待集
  1. 进入入口集,等待对象释放锁
  2. 对象锁持有者释放锁,当前线程获取锁
  3. 当前线程在syncronized中调用wait方法,释放monitor锁,进入等待集
  4. 其他线程调用notify或者notifyAll方法,使线程重新竞争monitor锁
  5. 线程重新获取锁
  6. 线程syncronized 方法或者代码块执行完毕,正常释放锁

常见问题

1、为什么 wait 必须在 synchronized 保护的同步代码中使用?

wait不加同步可能陷入永久等待或者死锁,同时wait 方法会释放 monitor 锁,这也要求我们必须首先进入到 synchronized 内持有这把锁。

2、为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?

  1. 因为 Java 中每个对象都有一把称之为 monitor 监视器的锁,由于每个对象都可以上锁,这就要求在对象头中有一个用来保存锁信息的位置。这个锁是对象级别的,而非线程级别的,wait/notify/notifyAll 也都是锁级别的操作,它们的锁属于对象,所以把它们定义在 Object 类中是最合适,因为 Object 类是所有对象的父类。
  2. 因为如果把 wait/notify/notifyAll 方法定义在 Thread 类中,会带来很大的局限性,比如一个线程可能持有多把锁,以便实现相互配合的复杂逻辑,假设此时 wait 方法定义在 Thread 类中,如何实现让一个线程持有多把锁呢?又如何明确线程等待的是哪把锁呢?既然我们是让当前线程去等待某个对象的锁,自然应该通过操作对象来实现,而不是操作线程。

    3、wait/notify 和 sleep 方法的异同?

    相同点:

  3. 它们都可以让线程阻塞。

  4. 它们都可以响应 interrupt 中断:在等待的过程中如果收到中断信号,都可以进行响应,并抛出 InterruptedException 异常。

不同点:

  1. wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。
  2. 在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁。
  3. sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。
  4. wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法

生产者消费者模式

  1. package com.imooc.thread_demo.prodconsumermodel;
  2. import java.util.Date;
  3. import java.util.LinkedList;
  4. import java.util.stream.IntStream;
  5. /**
  6. * @Author: zhangjx
  7. * @Date: 2020/9/9 23:25
  8. * @Description:
  9. */
  10. public class ProducerConsumerModel {
  11. public static void main(String[] args) throws InterruptedException {
  12. StoargeQueue stoargeQueue = new StoargeQueue();
  13. Producer producer = new Producer(stoargeQueue);
  14. Consumer consumer = new Consumer(stoargeQueue);
  15. new Thread(producer).start();
  16. Thread.sleep(100);
  17. new Thread(consumer).start();
  18. }
  19. static class Producer implements Runnable{
  20. private StoargeQueue stoargeQueue;
  21. Producer(StoargeQueue stoargeQueue){
  22. this.stoargeQueue = stoargeQueue;
  23. }
  24. @Override
  25. public void run() {
  26. IntStream.range(0,20).forEach(e -> {
  27. stoargeQueue.put();
  28. //睡眠1ms 让消费者竞争minitor锁
  29. try {
  30. Thread.sleep(1);
  31. } catch (InterruptedException interruptedException) {
  32. interruptedException.printStackTrace();
  33. }
  34. });
  35. System.out.println("生产者退出");
  36. }
  37. }
  38. static class Consumer implements Runnable{
  39. private StoargeQueue stoargeQueue;
  40. Consumer(StoargeQueue stoargeQueue){
  41. this.stoargeQueue = stoargeQueue;
  42. }
  43. @Override
  44. public void run() {
  45. IntStream.range(0,20).forEach(e -> {
  46. stoargeQueue.get();
  47. try {
  48. Thread.sleep(1);
  49. } catch (InterruptedException interruptedException) {
  50. interruptedException.printStackTrace();
  51. }
  52. });
  53. System.out.println("消费者退出");
  54. }
  55. }
  56. static class StoargeQueue {
  57. private int maxSize = 3;
  58. private LinkedList<String> list = new LinkedList<>();
  59. public StoargeQueue() {
  60. }
  61. public synchronized void put(){
  62. while(list.size() == maxSize){
  63. System.out.println("存储队列已满,生产者停止生产");
  64. try {
  65. wait();
  66. } catch (InterruptedException e) {
  67. e.printStackTrace();
  68. }
  69. System.out.println("生产者被唤醒,继续生产");
  70. }
  71. list.add(String.valueOf(new Date()));
  72. System.out.println("生产者生产====================");
  73. notify();
  74. }
  75. public synchronized void get(){
  76. while (list.size() == 0){
  77. System.out.println("存储队列已空,消费者停止消费");
  78. notify();
  79. try {
  80. wait();
  81. } catch (InterruptedException e) {
  82. e.printStackTrace();
  83. }
  84. System.out.println("消费者被唤醒,继续消费");
  85. }
  86. list.poll();
  87. System.out.println("消费者消费====================");
  88. }
  89. }
  90. }

输出

  1. "C:\Program Files\Java\jdk1.8.0_171\bin\java.exe" "-javaagent:D:\idea2020\IntelliJ IDEA 2020.1.2\lib\idea_rt.jar=64225:D:\idea2020\IntelliJ IDEA 2020.1.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_171\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_171\jre\lib\rt.jar;C:\Users\z\Desktop\mooc\多线程\thread_demo\target\classes;E:\repository\org\springframework\boot\spring-boot-starter\2.2.2.RELEASE\spring-boot-starter-2.2.2.RELEASE.jar;E:\repository\org\springframework\boot\spring-boot\2.2.2.RELEASE\spring-boot-2.2.2.RELEASE.jar;E:\repository\org\springframework\spring-context\5.2.2.RELEASE\spring-context-5.2.2.RELEASE.jar;E:\repository\org\springframework\spring-aop\5.2.2.RELEASE\spring-aop-5.2.2.RELEASE.jar;E:\repository\org\springframework\spring-beans\5.2.2.RELEASE\spring-beans-5.2.2.RELEASE.jar;E:\repository\org\springframework\spring-expression\5.2.2.RELEASE\spring-expression-5.2.2.RELEASE.jar;E:\repository\org\springframework\boot\spring-boot-autoconfigure\2.2.2.RELEASE\spring-boot-autoconfigure-2.2.2.RELEASE.jar;E:\repository\org\springframework\boot\spring-boot-starter-logging\2.2.2.RELEASE\spring-boot-starter-logging-2.2.2.RELEASE.jar;E:\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;E:\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;E:\repository\org\apache\logging\log4j\log4j-to-slf4j\2.12.1\log4j-to-slf4j-2.12.1.jar;E:\repository\org\apache\logging\log4j\log4j-api\2.12.1\log4j-api-2.12.1.jar;E:\repository\org\slf4j\jul-to-slf4j\1.7.29\jul-to-slf4j-1.7.29.jar;E:\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\repository\org\springframework\spring-core\5.2.2.RELEASE\spring-core-5.2.2.RELEASE.jar;E:\repository\org\springframework\spring-jcl\5.2.2.RELEASE\spring-jcl-5.2.2.RELEASE.jar;E:\repository\org\yaml\snakeyaml\1.25\snakeyaml-1.25.jar;E:\repository\org\springframework\boot\spring-boot-devtools\2.2.2.RELEASE\spring-boot-devtools-2.2.2.RELEASE.jar;E:\repository\org\springframework\boot\spring-boot-configuration-processor\2.2.2.RELEASE\spring-boot-configuration-processor-2.2.2.RELEASE.jar;E:\repository\org\projectlombok\lombok\1.18.10\lombok-1.18.10.jar;E:\repository\org\slf4j\slf4j-api\1.7.29\slf4j-api-1.7.29.jar" com.imooc.thread_demo.prodconsumermodel.ProducerConsumerModel
  2. 生产者生产====================
  3. 生产者生产====================
  4. 生产者生产====================
  5. 存储队列已满,生产者停止生产
  6. 消费者消费====================
  7. 消费者消费====================
  8. 消费者消费====================
  9. 存储队列已空,消费者停止消费
  10. 生产者被唤醒,继续生产
  11. 生产者生产====================
  12. 消费者被唤醒,继续消费
  13. 消费者消费====================
  14. 生产者生产====================
  15. 消费者消费====================
  16. 存储队列已空,消费者停止消费
  17. 生产者生产====================
  18. 消费者被唤醒,继续消费
  19. 消费者消费====================
  20. 存储队列已空,消费者停止消费
  21. 生产者生产====================
  22. 消费者被唤醒,继续消费
  23. 消费者消费====================
  24. 生产者生产====================
  25. 消费者消费====================
  26. 存储队列已空,消费者停止消费
  27. 生产者生产====================
  28. 消费者被唤醒,继续消费
  29. 消费者消费====================
  30. 生产者生产====================
  31. 消费者消费====================
  32. 生产者生产====================
  33. 消费者消费====================
  34. 生产者生产====================
  35. 消费者消费====================
  36. 生产者生产====================
  37. 消费者消费====================
  38. 存储队列已空,消费者停止消费
  39. 生产者生产====================
  40. 消费者被唤醒,继续消费
  41. 消费者消费====================
  42. 存储队列已空,消费者停止消费
  43. 生产者生产====================
  44. 消费者被唤醒,继续消费
  45. 消费者消费====================
  46. 生产者生产====================
  47. 消费者消费====================
  48. 生产者生产====================
  49. 消费者消费====================
  50. 生产者生产====================
  51. 消费者消费====================
  52. 生产者生产====================
  53. 消费者消费====================
  54. 生产者生产====================
  55. 消费者消费====================
  56. 消费者退出
  57. 生产者退出
  58. Process finished with exit code 0

两线程交替打印

  1. package com.imooc.thread_demo.waitnotify;
  2. /**
  3. * @Author: zhangjx
  4. * @Date: 2020/9/10 16:02
  5. * @Description: 两个线程交替打印
  6. */
  7. public class WaitNotifyTwoThreadRoundPrint {
  8. private static int count = 0;
  9. public static void main(String[] args) throws InterruptedException {
  10. Object lock = new Object();
  11. ThreadPrint threadA = new ThreadPrint(lock);
  12. ThreadPrint threadB = new ThreadPrint(lock);
  13. new Thread(threadA).start();
  14. Thread.sleep(100);
  15. new Thread(threadB).start();
  16. }
  17. static class ThreadPrint implements Runnable{
  18. private Object lock;
  19. ThreadPrint(Object lock){
  20. this.lock = lock;
  21. }
  22. @Override
  23. public void run() {
  24. while(count <= 100){
  25. synchronized (lock){
  26. System.out.println(Thread.currentThread().getName() + ":" + count++);
  27. lock.notify();
  28. try {
  29. if(count <= 100){
  30. lock.wait();
  31. }
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. }
  37. }
  38. }
  39. }