在现实应用中,很多时候需要让多个线程按照一定的次序来访问共享资源。例如,经典的生产者和消费者问题。
① 这类问题描述了这样一种情况,假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。如果仓库中没有产品,则生产者可以将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,知道仓库中再次放入产品为止。
② 显然,这是一个同步问题,生产者和消费者共享同一资源,并且,生产者和消费者之间彼此依赖,互为条件向前推进。
该如何变成程序来解决这个问题呢?
传统的思路是利用循环检测的方式来实现,通过重复检查某一个特定条件是否成立来决定线程的推进顺序。
比如,一旦生产者生产结束,它就继续利用循环检测来判断仓库中的产品是否被消费者消费,而消费者也是在消费结束后就会立即使用循环检测的方式来判断仓库中是否又放进产品。显然,这些操作是很耗CPU资源的,不值得提倡。
有没有更好的方法来解决这类问题呢?
Java提供了3个重要的方法巧妙解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。
① wait():可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
② notify():可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行状态。
③ notifyAll():可以使所有正在等待队列中同一共享资源的线程从等待队列状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。
示例:生产者采摘5个苹果放入篮子中,消费者拿走篮子中的5个苹果后,生产者再采摘5个放入篮子…共进行4次。
代码如下:
1 //消费者线程 2 class Consumer extends Thread { 3 private Basket basket = null; 4 5 public Consumer(Basket basket) { 6 super(); 7 this.basket = basket; 8 } 9 10 @Override 11 public void run() { 12 basket.popApple(); 13 } 14 15 } 16 17 //生产者线程 18 class Productor extends Thread { 19 private Basket basket = null; 20 21 public Productor(Basket basket) { 22 super(); 23 this.basket = basket; 24 } 25 26 @Override 27 public void run() { 28 basket.pushApple(); 29 } 30 31 } 32 33 //篮子类 34 class Basket { 35 private LinkedList<Apple> basket = new LinkedList<Apple>(); 36 37 // 放4轮苹果 38 public synchronized void pushApple() { 39 for (int i = 0; i < 20; i++) { 40 Apple apple = new Apple(i); 41 push(apple); 42 } 43 } 44 45 // 取4轮苹果 46 public synchronized void popApple() { 47 for (int i = 0; i < 20; i++) { 48 pop(); 49 } 50 } 51 52 // 向篮子中放苹果 53 private void push(Apple apple) { 54 // 当篮子中存放了5个苹果就等待并通知消费者来消费 55 if (basket.size() == 5) { 56 try { 57 wait(); 58 } catch (InterruptedException e) { 59 e.printStackTrace(); 60 }// 等待并释放当前资源的锁 61 } 62 try { 63 Thread.sleep(500); 64 } catch (InterruptedException e) { 65 e.printStackTrace(); 66 } 67 basket.addFirst(apple); 68 System.out.println("存放:" + apple.toString()); 69 notify();// 通知消费者来消费 70 } 71 72 // 向篮子中取苹果 73 private void pop() { 74 // 当篮子中苹果为0时就等待并通知生产者来生产 75 if (basket.size() == 0) { 76 try { 77 wait(); 78 } catch (InterruptedException e) { 79 e.printStackTrace(); 80 }// 等待并释放当前资源的锁 81 } 82 try { 83 Thread.sleep(500); 84 } catch (InterruptedException e) { 85 e.printStackTrace(); 86 } 87 Apple apple = basket.removeFirst(); 88 System.out.println("吃掉:" + apple.toString()); 89 notify();// 通知生产者来生产 90 } 91 } 92 93 // 苹果类 94 class Apple { 95 private int id; 96 97 public Apple(int id) { 98 this.id = id; 99 } 100 101 @Override 102 public String toString() { 103 return "苹果:" + (id + 1); 104 } 105 }
主方法:
1 Basket basket=new Basket(); 2 Productor productor=new Productor(basket); 3 Consumer consumer=new Consumer(basket); 4 productor.start(); 5 consumer.start();
运行结果:
生产者逐个生产5个苹果1-5;消费者逐个消费5个苹果5-1;
生产者逐个生产5个苹果6-10;消费者逐个消费5个苹果10-6;
生产者逐个生产5个苹果11-15;消费者逐个消费5个苹果15-11;
生产者逐个生产5个苹果16-20;消费者逐个消费5个苹果20-16;