一.线程间通信的定义
由于默认情况下CPU是随机切换线程的,多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作,即多个线程在操作同一份数据时,避免对同一共享变量的争夺。于是我们引出了等待唤醒机制(wait()、notify()),即在一个线程进行了规定操作后,就进入了等待状态(wait),等待其它线程执行完指定代码后,再将其唤醒(notify)。
1.wait方法:在其它线程调用此对象的notify或notifyAll方法之前,导致当前线程等待;作用为释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify或notifyAll方法),这样它才能重新获得锁的拥有权和恢复执行;要确保调用wait方法时拥有锁,即wait方法的调用必须放在synchronized方法或synchronized块中;
2.notify方法:随机唤醒一个等待当前对象的锁的线程,唤醒在此对象监视器上等待的单个线程;
3.notifyAll方法:唤醒在此对象监视器上等待的所有线程;
4.以上方法都定义在Object类中,因为:JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单地说,由于wait,notify和notifyAll都是锁级别的操作,所以把它们定义在Object类中因为锁属于对象;
5.以上方法都要在同步方法或同步代码块中调用:当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的,当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁,以便其它在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以它们只能在同步方法或者同步块中被调用。如果你不这么做,代码会抛出IllegalMonitorStateException异常。
二.生产者-消费者的代码示例:
(1)面包类
class Bread { private int bid;// 面包的id private int num;// 面包的个数 public synchronized void produce(String name) {// 生产方法 if (num != 0) {// 当面包个数不为0,不生产,处于等待状态 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } bid++;// 当面包个数为0,开始生产 num++; System.out.println(name + " Bread produce "+ bid); notify(); } public synchronized void consume(String name) {// 同步方法 if (num == 0) {// 当面包数量为0,无面包可供消费,等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num--;// 当面包数量不为0,有面包可以开始消费 System.out.println(name + " Bread consume " + bid); notify(); } }
(2)生产者类
class Producer implements Runnable { private Bread bread; private String name;// 生产者类的名称 public Producer(Bread bread, String name) { this.bread = bread; this.name = name; } @Override public void run() { for (int i = 0; i < 40; i++) { bread.produce(name); } } }
(3)消费者类
class Consumer implements Runnable { private Bread bread; private String name;// 消费者类的名称 public Consumer(Bread bread, String name) { this.bread = bread; this.name = name; } @Override public void run() { for (int i = 0; i < 40; i++) { bread.consume(name); } } }
(4)测试类
public class Test { public static void main(String[] args) { Bread bread = new Bread(); new Thread(new Producer(bread, "p1")).start(); new Thread(new Consumer(bread, "c1")).start(); } }
(5)测试结果
p1 Bread produce 1
c1 Bread consume 1
p1 Bread produce 2
c1 Bread consume 2
...... ......
p1 Bread produce 38
c1 Bread consume 38
p1 Bread produce 39
c1 Bread consume 39
p1 Bread produce 40
c1 Bread consume 40
综上所述,生产者和消费者之间利用了wait和notify两个方法完成了两个不同线程操作同一数据的操作。
三.多个生产者与消费者
当遇到多个生产者与消费者时,就需要用到notifyAll方法,另外需要将Bread类中的if循环换成while循环。
测试代码如下:
public class Test { public static void main(String[] args) { Bread bread = new Bread(); new Thread(new Producer(bread, "p1")).start(); new Thread(new Consumer(bread, "c1")).start(); new Thread(new Producer(bread, "p2")).start(); new Thread(new Consumer(bread, "c2")).start(); new Thread(new Producer(bread, "p3")).start(); new Thread(new Consumer(bread, "c3")).start(); new Thread(new Producer(bread, "p4")).start(); new Thread(new Consumer(bread, "c4")).start(); } }
测试结果:
p1 Bread produce 1 c1 Bread consume 1 p1 Bread produce 2 c1 Bread consume 2 p2 Bread produce 3 c1 Bread consume 3 p1 Bread produce 4 c2 Bread consume 4 p3 Bread produce 5 c1 Bread consume 5 p1 Bread produce 6 c3 Bread consume 6 p4 Bread produce 7 c2 Bread consume 7 p2 Bread produce 8 c1 Bread consume 8 ...... ...... p3 Bread produce 158 c3 Bread consume 158 p2 Bread produce 159 c4 Bread consume 159 p3 Bread produce 160 c3 Bread consume 160