wait、notify、notifyAll

wait、notify、notifyAll这三个方法都是Object类的final修饰的方法,无法被重写,所有对象都具有该方法,先简单了解这三个方法的作用:
某个对象在当前线程中调用它的wait()方法能让当前线程阻塞,当然前提是当前线程必须拥有此对象的锁才能调用wait()方法,因此wait()方法必须在同步块或者同步方法中调用(synchronized块或者synchronized方法)。某个对象在当前线程调用了wait()方法,相当于让当前线程交出该对象的锁,然后让当前线程进入该对象的等待池,等待池中的线程不会去竞争该对象的锁。当该对象在某个线程中(该线程需要具有该对象锁)调用notifyAll()方法(唤醒所有wait线程)或notify()方法(只随机唤醒一个wait线程)就会唤醒该对象等待池中的wait线程,被唤醒的的wait线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。特别注意的是,一个wait线程被唤醒从等待池进入锁池不代表立刻就会获取该对象的锁,还要和锁池中的其它线程竞争,而且必须要等待在当前线程调用完对象的notifyAll()方法或notify()方法并退出synchronized块,释放对象锁后,其余线程才能竞争获取对象的锁。
参考博客:
https://www.cnblogs.com/dolphin0520/p/3920385.html
https://blog.csdn.net/ns_code/article/details/17225469

wait和sleep的区别

  • wait方法位于Object类,而sleep方法则位于Thread类;
  • wait必须在同步代码块(或同步方法)中调用,而sleep则没有要求;
  • 使用wait会释放锁,而sleep则不会释放锁;

    生产者和消费者模式介绍

    生产者和消费者模式是经典的一类线程协作问题,比如有两个进程A和B,它们共享一个固定大小的缓冲区,A进程产生数据放入缓冲区,B进程从缓冲区中取出数据进行计算,那么这里其实就是一个生产者和消费者的模式,A相当于生产者,B相当于消费者,如图:
    线程协作 - 图1
    生产者消费者模式可以达到一种生产者和消费者的平衡,可以保证生产者不会再缓冲区满的时候放入数据,消费者也不会在缓冲区空的时候取走数据:当缓冲区满的时候,生产者会进入等待(阻塞状态),只有当缓冲区没有满的时候生产者才会被唤醒;而当缓冲区空的时候,消费者会进入等待(阻塞状态),只有当缓冲区非空的时候才会被唤醒。

生产者消费者模式优点:

  • 解耦:将生产者类和消费者类进行解耦,消除代码之间的依赖性,简化工作负载的管理
  • 复用:通过将生产者类和消费者类独立开来,那么可以对生产者类和消费者类进行独立的复用与扩展
  • 调整并发数:由于生产者和消费者的处理速度是不一样的,可以调整并发数,给予慢的一方多的并发数,来提高任务的处理速度
  • 异步:对于生产者和消费者来说能够各司其职,生产者只需要关心缓冲区是否还有数据,不需要等待消费者处理完;同样的对于消费者来说,也只需要关注缓冲区的内容,不需要关注生产者,通过异步的方式支持高并发,将一个耗时的流程拆成生产和消费两个阶段,这样生产者因为执行put()的时间比较短,而支持高并发
  • 支持分布式:生产者和消费者通过队列进行通讯,所以不需要运行在同一台机器上,在分布式环境中可以通过redis的list作为队列,而消费者只需要轮询队列中是否有数据。同时还能支持集群的伸缩性,当某台机器宕掉的时候,不会导致整个集群宕掉

    wait()/notify()方法实现生产者消费者模式

    创建缓冲区:

    1. public class Container {
    2. //缓冲区
    3. List<Integer> list = new LinkedList<>();
    4. //规定缓冲区大小为10
    5. int capacity = 10;
    6. public synchronized void put(Integer value) {
    7. try {
    8. //模拟生成时间
    9. Thread.sleep(1000);
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. }
    13. while (list.size() == capacity) {
    14. try {
    15. System.out.println("容器已经满了,请生产者等待...");
    16. wait();
    17. } catch (InterruptedException e) {
    18. e.printStackTrace();
    19. }
    20. }
    21. list.add(value);
    22. System.out.println("容器没有满,生产者" + Thread.currentThread().getName() + "放进的value:" + value + ",容器大小" + list.size());
    23. //唤醒其他所有处于wait()的线程,包括消费者和生产者
    24. notifyAll();
    25. }
    26. public synchronized void take() {
    27. try {
    28. //模拟消费时间
    29. Thread.sleep(1000);
    30. } catch (InterruptedException e) {
    31. e.printStackTrace();
    32. }
    33. while (list.size() == 0) {
    34. try {
    35. System.out.println("容器是空的,请消费者等待...");
    36. wait();
    37. } catch (InterruptedException e) {
    38. e.printStackTrace();
    39. }
    40. }
    41. System.out.print("容器不是空的,消费者" + Thread.currentThread().getName() + "拿走的value:" + list.get(0));
    42. list.remove(0);
    43. System.out.println(",容器大小:" + list.size());
    44. //唤醒其它所有处于wait的线程
    45. notifyAll();
    46. }
    47. }

    创建生产者:

    public class Producer implements Runnable{
      private final Container container;
    
      public Producer(Container container) {
          this.container = container;
      }
    
      @Override
      public void run() {
          container.put(new Random().nextInt(100));
      }
    }
    

    创建消费者:

    public class Customer implements Runnable {
      private final Container container;
    
      public Customer(Container container) {
          this.container = container;
      }
    
      @Override
      public void run() {
          container.take();
      }
    }
    

    创建测试类:

    public class Main {
      public static void main(String[] args) {
          Container container = new Container();
    
          for (int i = 0; i < 10; i++) {
              new Thread(new Producer(container)).start();
          }
    
          for (int i = 0; i < 6; i++) {
              new Thread(new Customer(container)).start();
          }
    
      }
    }
    

    从测试类来看,最后缓冲区的大小应该是4,下面是测试结果:
    image.png
    注意:缓冲区的代码使用的条件判断是while而不是if,使用if会发生虚假唤醒,出现虚假唤醒的原因是从
    阻塞态到就绪态再到运行态没有进行判断,我们只需要让其每次得到操作权时都进行判断就可以了。虚假唤醒的参考文章:https://juejin.cn/post/6903789756954443784
    除了wait和notify方法外,juc还提供了Condition类的await和signal方法。