一、线程间通信
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不同,比如著名的生产者消费者模型
注意:sleep不放弃锁,wait会放弃锁
二、等待唤醒机制【合作关系】
线程之间的关系:
- 竞争:争夺锁。
- 协作:一个线程进行了规定操作后,就进入等待状态
wait()
, 等待其他线程执行完他们的指定代码过后 再将其唤醒notify()
;在有多个线程进行等待时, 如果需要,可以使用notifyAll()
来唤醒所有的等待线程
wait/notify 就是线程间的一种协作机制
等待唤醒中的方法:等待唤醒机制就是用于解决线程间通信的问题
- wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
- notify:【唤醒的线程的随机的】则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
- notifyAll:则释放所通知对象的 wait set 上的全部线程。
notice:哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
总结如下:
- 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
- 否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态
调用wait和notify方法需要注意的细节
- wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
- 锁对象可以是任意对象.wait方法与notify方法是属于Object类的方法的。因为:而任意对象的所属类都是继承了Object类的。
- wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
容易导致的错误理解--经过多次试验,发现,等待唤醒机制只使用于一个生产者和一个消费者这种模型关系;如果是类似于一个生产者和多个消费者,可能会发生多个消费者的wait操作会相互干扰;比如,一个生产者a,两个消费者b,c,当一个消费者b正在wait等待时,锁被释放,却被另一个消费者c拿到(逻辑上应该让生产者拿到锁进行操作),而此时消费者c也进入了wait状态,导致消费者b的wait逻辑上异常停止。程序出现了BUG!**
正确的理解:notify唤醒的wait是随机的,由于消费者最后都会notify,但是唤醒的可能是另一个正在wait的而不该被wait的消费者而产生了错觉!
如下演示:
- 产品:
public class Goods {
boolean exist = false;
}
- 生产者
public class Producer implements Runnable {
Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while(true) {
// 故意不让生产者很容易抢到锁
try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (goods){
if(goods.exist){
try { goods.wait(); // 注意notify的随机叫醒。
} catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println(Thread.currentThread().getName() + "生产了。");
goods.exist = true;
goods.notify();
}
}
}
}
- 消费者
public class Consumer implements Runnable{
Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while(true) {
synchronized (goods){
if(!goods.exist){
try {
System.out.println(Thread.currentThread().getName() + "拿到锁了。");
goods.wait();
System.out.println(Thread.currentThread().getName() + "释放锁了");
// if(!goods.exist) {
// goods.notify();
// continue;
// }
} catch (InterruptedException e) { e.printStackTrace(); }
}
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.print(Thread.currentThread().getName() + "消费了。");
System.out.println("逻辑上,exist应该是 true,而实际值 = " + goods.exist);
goods.exist = false;
goods.notify();
}
}
}
}
- 测试函数
public class Main {
public static void main(String[] args) {
Goods goods = new Goods();
Producer producer = new Producer(goods);
Consumer consumer = new Consumer(goods);
new Thread(producer,"--生产者 ").start();
new Thread(consumer,"@@消费者 1").start();
new Thread(consumer,"@@消费者 2").start();
new Thread(consumer,"@@消费者 3").start();
new Thread(consumer,"@@消费者 4").start();
new Thread(consumer,"@@消费者 5").start();
new Thread(consumer,"@@消费者 6").start();
}
}
- 运行结果(一个消费者导致另一个消费者非正常唤醒)
@@消费者 1拿到锁了。
@@消费者 6拿到锁了。
@@消费者 5拿到锁了。
@@消费者 4拿到锁了。
@@消费者 3拿到锁了。
@@消费者 2拿到锁了。
--生产者 生产了。
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = true
@@消费者 1拿到锁了。
@@消费者 6释放锁了
@@消费者 6消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 5释放锁了
@@消费者 5消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 5拿到锁了。
@@消费者 4释放锁了
@@消费者 4消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 3释放锁了
@@消费者 3消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 2释放锁了
@@消费者 2消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 5释放锁了
@@消费者 5消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1拿到锁了。
@@消费者 2拿到锁了。
@@消费者 3拿到锁了。
--生产者 生产了。
@@消费者 4消费了。逻辑上,exist应该是 true,而实际值 = true
@@消费者 6拿到锁了。
@@消费者 2释放锁了
@@消费者 2消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 5拿到锁了。
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1拿到锁了。
@@消费者 6释放锁了
@@消费者 6消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 6拿到锁了。
@@消费者 5释放锁了
@@消费者 5消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1拿到锁了。
@@消费者 6释放锁了
@@消费者 6消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 6拿到锁了。
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1拿到锁了。
@@消费者 6释放锁了
@@消费者 6消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 6拿到锁了。
@@消费者 1释放锁了
@@消费者 1消费了。逻辑上,exist应该是 true,而实际值 = false
@@消费者 1拿到锁了。
@@消费者 6释放锁了
Process finished with exit code -1