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相当于消费者,如图:
生产者消费者模式可以达到一种生产者和消费者的平衡,可以保证生产者不会再缓冲区满的时候放入数据,消费者也不会在缓冲区空的时候取走数据:当缓冲区满的时候,生产者会进入等待(阻塞状态),只有当缓冲区没有满的时候生产者才会被唤醒;而当缓冲区空的时候,消费者会进入等待(阻塞状态),只有当缓冲区非空的时候才会被唤醒。
生产者消费者模式优点:
- 解耦:将生产者类和消费者类进行解耦,消除代码之间的依赖性,简化工作负载的管理
- 复用:通过将生产者类和消费者类独立开来,那么可以对生产者类和消费者类进行独立的复用与扩展
- 调整并发数:由于生产者和消费者的处理速度是不一样的,可以调整并发数,给予慢的一方多的并发数,来提高任务的处理速度
- 异步:对于生产者和消费者来说能够各司其职,生产者只需要关心缓冲区是否还有数据,不需要等待消费者处理完;同样的对于消费者来说,也只需要关注缓冲区的内容,不需要关注生产者,通过异步的方式支持高并发,将一个耗时的流程拆成生产和消费两个阶段,这样生产者因为执行put()的时间比较短,而支持高并发
支持分布式:生产者和消费者通过队列进行通讯,所以不需要运行在同一台机器上,在分布式环境中可以通过redis的list作为队列,而消费者只需要轮询队列中是否有数据。同时还能支持集群的伸缩性,当某台机器宕掉的时候,不会导致整个集群宕掉
wait()/notify()方法实现生产者消费者模式
创建缓冲区:
public class Container {//缓冲区List<Integer> list = new LinkedList<>();//规定缓冲区大小为10int capacity = 10;public synchronized void put(Integer value) {try {//模拟生成时间Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}while (list.size() == capacity) {try {System.out.println("容器已经满了,请生产者等待...");wait();} catch (InterruptedException e) {e.printStackTrace();}}list.add(value);System.out.println("容器没有满,生产者" + Thread.currentThread().getName() + "放进的value:" + value + ",容器大小" + list.size());//唤醒其他所有处于wait()的线程,包括消费者和生产者notifyAll();}public synchronized void take() {try {//模拟消费时间Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}while (list.size() == 0) {try {System.out.println("容器是空的,请消费者等待...");wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("容器不是空的,消费者" + Thread.currentThread().getName() + "拿走的value:" + list.get(0));list.remove(0);System.out.println(",容器大小:" + list.size());//唤醒其它所有处于wait的线程notifyAll();}}
创建生产者:
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,下面是测试结果:

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