前一例只是简单地为特定对象或方法加锁,但有时情况会更加复杂,如果两个线程之间有依
赖关系,线程之间必须进行通信,互相协调才能完成工作。
例如有一个经典的堆栈问题,一个线程生成了一些数据,将数据压栈;另一个线程消费了这些数据, 将数据出栈。这两个线程互相依赖,当堆栈为空时,消费线程无法取出数据时,应该通知生成线程添 加数据;当堆栈已满时,生产线程无法添加数据时,应该通知消费线程取出数据。
为了实现线程间通信,需要使用Object类中声明的5个方法:
void wait():使当前线程释放对象锁,然后当前线程处于对象等待队列中阻塞状态。
void wait(long timeout):同wait()方法,等待timeout毫秒时间。
void wait(long timeout, int nanos):同wait()方法,等待timeout毫秒加nanos纳秒时间。
void notify():当前线程唤醒此对象等待队列中的一个线程。
void notifyAll():当前线程唤醒此对象等待队列中的所有线程。
如图所示:
线程有多种方式进入阻塞状态,除了通过wait()外还 有,加锁的方式和其他方式
下面看看消费和生产示例中堆栈类代码:
//堆栈类 public class Stack { // 堆栈指针初始值为0 private int pointer = 0; // 堆栈有五个字符的空间 private char[] data = new char[5]; // 压栈方法,加上互斥锁 public synchronized void push(char c){ // 堆栈已满,不能压栈 while (pointer == data.length){ try{ // 等待,直到所有数据出栈 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 通知其他线程把数据出栈 this.notify(); // 数据压栈 data[pointer] = c; // 指针向上移动 pointer++; } // 出栈方法 public synchronized char pop(){ // 堆栈无数据,不能出栈 while (pointer == 0){ try { // 等待其他线程把数据压出栈 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 通知其他线程把数据出栈 this.notify(); // 指针向下移动 pointer--; return data[pointer]; } }
上述代码实现了同步堆栈类,该堆栈有最多5个元素的空间,代码声明了压栈方法push(),该方 法是一个同步方法,在该方法中首先判断是否堆栈已满,如果已满不能压栈,调用this.wait()让当前线 程进入对象等待状态中。如果堆栈未满,程序会往下运行调用this.notify()唤醒对象等待队列中的一个线程。
调用代码:
public class HelloWorld { public static void main(String[] args) { Stack stack = new Stack(); // 下面的消费者和生产者所操作的是同一个堆栈对象stack // 生产者线程 Thread producer = new Thread(() ->{ char c ; for (int i = 0;i<10;i++){ // 随机产生10个字符 c = (char)(Math.random()*26 + 'a'); // 把字符压栈 stack.push(c); // 打印字符 System.out.println("生产:"+c); try { // 每生产一个字符就线程休眠 Thread.sleep((int)(Math.random()*1000)); } catch (InterruptedException e) { e.printStackTrace(); } } }); // 消费者线程 Thread consumer = new Thread(() ->{ char c; for (int i = 0;i<10;i++){ // 从堆栈中读取字符 c = stack.pop(); // 打印字符 System.out.println("消费:"+c); try { // 每读取一个字符就线程休眠 Thread.sleep((int)(Math.random()*1000)); } catch (InterruptedException e) { e.printStackTrace(); } } }); // 启动生产者线程 producer.start(); // 启动消费者线程 consumer.start(); } }
运行结果:
生产:x 消费:x 生产:s 消费:s 生产:d 消费:d 生产:p 消费:p 生产:v 消费:v 生产:r 消费:r 生产:p 消费:p 生产:j 消费:j 生产:j 消费:j 生产:d 消费:d