wait和notify实现生产者消费者模型
程序员文章站
2022-07-12 19:33:36
...
读这篇文章之前,首先要弄明白java对象的两个方法,wait和notify或者notifyAll,那弄懂这两个方法又要知道一个概念,java中Object类有个对象锁,所有的对象都继承自Object类,因此每个对象都有个锁,而且java中的对象锁在同一时刻只能由一个线程持有,这是java在多线程编程中实现互斥的基础。 那一个线程如何获得一个对象的锁呢?根据JDK源码,一个线程可以有3种方法获得对象锁,第一种是执行该对象实例的synchronized方法,第二种是执行对象中的synchronized代码块,第三种是执行类(非实例)的静态synchronized方法,最后需要注意的是一个对象的锁在同一个时间只能由一个线程持有,我们今天将要讨论的3个方法就是在线程获得了对象锁的情况下执行的。
先说一下wait方法,该方法的作用就是暂停当前线程,并释放占有的对象锁(当前线程一定要占有对象锁,否则会抛出异常),区别于sleep方法,sleep方法是Thread类的方法,也是暂停当前线程,但并不会释放锁(如果占有的话)。
接下来说notifyAll方法,作用是唤醒所有等待当前线程持有的对象锁的线程,同样的道理,调用notifyAll方法的线程必须持有对象锁,对象锁并不会在调用完notifyAll方法之后立刻被释放,需要等待当前线程(或者持有对象锁的方法,代码块)结束后才会被释放,锁被释放后,其它被唤醒的线程就会随机竞争,先得到该对象锁的线程将会进入执行, notify和notifyAll的区别就是notify只会随机唤醒一个线程,notifyAll会唤醒所有调用wait的线程。
以下是运用wait和notify实现的生产者消费者的代码
由于ProductStack的push和pop方法都是synchronized的,而前面我们介绍synchronized方法要求必须获取对象锁才能执行,而同一时间又只能有一个线程可以获取对象锁,因此同一个时间只能有一个方法执行,要么在生产,要么在消费。
先说一下wait方法,该方法的作用就是暂停当前线程,并释放占有的对象锁(当前线程一定要占有对象锁,否则会抛出异常),区别于sleep方法,sleep方法是Thread类的方法,也是暂停当前线程,但并不会释放锁(如果占有的话)。
接下来说notifyAll方法,作用是唤醒所有等待当前线程持有的对象锁的线程,同样的道理,调用notifyAll方法的线程必须持有对象锁,对象锁并不会在调用完notifyAll方法之后立刻被释放,需要等待当前线程(或者持有对象锁的方法,代码块)结束后才会被释放,锁被释放后,其它被唤醒的线程就会随机竞争,先得到该对象锁的线程将会进入执行, notify和notifyAll的区别就是notify只会随机唤醒一个线程,notifyAll会唤醒所有调用wait的线程。
以下是运用wait和notify实现的生产者消费者的代码
package cn.javass.web.controller; import java.util.Stack; public class TestThead2 { public static void main(String[] args) { ProductStack ps = new ProductStack(); Thread p = new Thread(new Producer(ps)); Thread c = new Thread(new Consumer(ps)); p.start(); c.start(); } } //模拟一个产品,包括一个属性id class Product { String id; Product(String id) { this.id = id; } } //仓库类 class ProductStack { //仓库有个最大存储容量 int index = 6; //仓库中货架,通过java.util.Stack来实现,主要用到push和pop两个方法 Stack<Product> products = new Stack<Product>(); //生产产品,并放入货架 public synchronized void push(Product pc) { //如果货架上的产品已经满了,则停止生产 if (products.size() == index) { try { //一旦调用wait方法,当前线程将暂停,同时释放ProductStack的对象锁, //直到其它拥有该对象锁的线程调用notify或者notifyAll,当前线程才会 //尝试再次获取ProductStack的对象锁,并从此处继续执行 wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //生产一个产品 products.push(pc); System.out.println("生产了 " + pc.id); //此时货架肯定不是空的,通知消费者消费, 之前讲到,notify和notifyAll的区别是 //前者随机唤醒一个等待ProductStack对象锁的线程,而后者是唤醒所有线程,此时当前 //进程中只有pop一个线程在等待对象锁,因此此处也可以使用notify, //思考一下,如果把notifyAll方法拿到products.push(pc);前面呢? //其实效果是一样的,上面说过,调用notify或者notifyAll方法虽然唤醒了等待线程, //但并不会释放对象锁,直到当前线程(或者持有对象锁的方法,代码块)结束后才会被释放, //因此pop线程虽然被唤醒,但由于拿不到ProductStack对象锁,依旧无法执行 notifyAll(); } //从货架上取出产品供消费 public synchronized void pop() { //如果货架上是空的,则停止消费 if (products.isEmpty()) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //消费一个产品 Product pc = products.pop(); System.out.println("消费了 " + pc.id); //此时货架肯定不是满的,通知生产者生产 notifyAll(); } } //生产者 class Producer implements Runnable { ProductStack ps; Producer(ProductStack ps) { this.ps = ps; } @Override public void run() { //本次生产计划为20件商品 for (int i = 0; i < 20; i++) { ps.push(new Product(String.valueOf(i))); } } } //消费者 class Consumer implements Runnable { ProductStack ps; Consumer(ProductStack ps) { this.ps = ps; } @Override public void run() { //消费计划为20件商品 for (int i =0;i<20;i++) { ps.pop(); //消费完一个商品之后停顿一段时间,接着消费,可以把这段代码注释掉,比较下输出结果有什么不同 try { Thread.sleep((int) (Math.random() * 20)); } catch (InterruptedException e) { e.printStackTrace(); } } } }
由于ProductStack的push和pop方法都是synchronized的,而前面我们介绍synchronized方法要求必须获取对象锁才能执行,而同一时间又只能有一个线程可以获取对象锁,因此同一个时间只能有一个方法执行,要么在生产,要么在消费。
下一篇: 线程池常用几种方式