线程协作
在多线程开发中,为避免共享资源数据错误,常使用互斥(synchronized)机制实现多线程对共享资源的访问同步。然而,当线程间需要相互提供支持的情况下,仅有互斥机制是不够的,这就是线程协作机制起作用的时候了。
-
线程协作是多线程互斥同步的基础上,使线程之间依照一定条件,有目的、有计划地交互协同工作,这是一种较高级的线程同步方式。
-
Java提供了一个精心设计的线程间通信机制即wait-notify机制,通过该机制可以实现线程协作。
-
wait-notify机制是通过使用wait()、notify()和notifyAll()三个方法来实现的。
这三个方法均定义在Object类中,是final修饰的实例方法。
这三个方法必须在synchronized代码中调用,而且只有通过锁对象才能调用这三个方法。 -
wait()方法
通过当前同步锁调用该方法的线程在当前同步锁下进入等待状态,直到其他线程通过相同的同步锁调用notify( )或者notifyAll()方法。 -
notify( )
通过当前同步锁调用该方法,唤醒在当前同步锁下等待的线程中的一个结束等待。 -
notifyAll()
通过当前同步锁调用该方法,唤醒在当前同步锁下等待的所有线程结束等待。
生产者和消费者问题
在多线程程序设计中,生产者和消费者问题是典型的需要线程协作的场景。这类问题一般存在两类线程和一个共享数据资源,一类是提供共享数据的线程即生产者,另一类消耗共享数据的线程即消费者。这类问题具有以下特点:
- 一方面为防止数据错误,生产者和消费者是互斥的,任何一方访问共享数据时,另一方不能访问共享数据;
- 另一方面生产者和消费互相需要对方。生产者不能无限制提供数据,当共享数据达到最大量时,需要让消费者消耗数据。消费者也不能无限制消耗数据,当已无共享数据,需要让生产者提供数据。
案例-面包店
说明
面包店的柜橱用来存放面包,面包师傅负责将烤好面包放到柜橱中,店面伙计负责将柜橱中的面包售出。存在以下两种问题:
- 柜橱放满了,新面包没处放;
- 柜橱空了,但是面包仍在销售中。
解决问题的办法
- 师傅检查橱柜,若橱柜未满放入面包,同时通知伙计有面包卖,若橱柜满师傅暂停工作,直到收到伙计通知可以放面包了。
- 伙计检查橱柜,若橱柜未空销售面包,同时通知师傅可以放面包,若橱柜空伙计暂停工作,直到收到师傅通知有面包卖。
代码实现
开发面包柜(共享数据资源)
//面包柜
public class Cupboard {
private int count;//面包数量
private int size;//面包容量
public Cupboard(int size ){
this.size=size;
}
//放入一个面包,该方法是同步方法,同步锁为this
public synchronized void add(){
while(this.isFull()) {
System.out.println(Thread.currentThread().getName()+":柜内面包数:"+count+",已满!等待中......");
try {
this.wait();//在当前同步锁对象(this)下等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName()+":放入一个面包,柜内面包数:"+count);
this.notifyAll();//唤醒在当前同步锁对象(this)下等待的所有线程
Thread.yield();//让步,给其它线程以运行机会
}
//取出一个面包,该方法是同步方法,同步锁为this
public synchronized void remove(){
while(this.isEmpty()) {
System.out.println(Thread.currentThread().getName()+":柜内面包数:"+count+",已空!等待中......");
try {
this.wait();//在当前同步锁对象(this)下等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println(Thread.currentThread().getName()+":取出一个面包,柜内面包数:"+count);
this.notifyAll();//唤醒在当前同步锁对象(this)下等待的所有线程
Thread.yield();//让步,给其它线程以运行机会
}
//面包柜是否为空
public boolean isEmpty(){
return count<1;
}
//面包柜是否已满
public boolean isFull(){
return count>=size;
}
}
测试代码
public class Test {
public static void main(String[] args) {
final Cupboard cupboard = new Cupboard(5);//创建面包柜,容量为5个面包
//创建三个面包师线程和三个伙计线程,并启动。每个面包师烤一百个面包,每个伙计销售一百个面包。
for(int i=1;i<=3;i++) {
//面包师线程
new Thread("面包师"+i) {
public void run() {
for(int i=0;i<100;i++) {
cupboard.add();
}
}
}.start();
//伙计线程
new Thread("伙计"+i) {
public void run() {
for(int i=0;i<100;i++) {
cupboard.remove();
}
}
}.start();
}
}
}
运行结果截图如下: