线程同步1中解决的是方法appendChar()在不同线程中改变StringBuilder对象而引发的不同步问题。进一步的问题是,如果现在有两个不同的方法A()和B(),他们在不同的线程中改变StringBuilder对象,这样引发的线程不同步该怎样避免?
解决上述问题的一种方法是线程中的沉睡(wait())和唤醒(notify())机制。同一时间要让方法A()和B()中的一个处于唤醒状态,一个处于沉睡状态。一个线程执行完毕,沉睡前唤醒另一个线程,如此往复,以保证线程的同步。
这就涉及到了多线程同步问题中一个很经典的案例——生产者和消费者。生产者把生产出的产品放到容器中,逐渐累积。容器被放满时要停止生产,进入沉睡状态(阻塞队列)。当容器不满时,给生产线程信息,开始生产(阻塞队列→就绪队列)。消费者消耗容器中的产品,如果容器中的产品被耗尽,则进入沉睡状态(阻塞队列),停止消耗。当容器进入新产品时,给消费线程信息,开始消耗(阻塞队列→就绪队列)。
代码示例如下:
public class ProduceAndConsumer {
private StringBuilder sb = new StringBuilder();
//生产——添加char
public synchronized StringBuilder appendChar(char a,int id){
//先判断容器是否装满
while(sb.length()>=5){//自设定 长度到5为满
try {
wait();//进入沉睡状态
} catch (InterruptedException e) {e.printStackTrace();}
}
sb.append(a);
System.out.println(id+",生产了:"+a +" 容器:"+sb);
this.notify();//已加入产品,唤醒消费线程(如果沉睡)
return sb;
}
//消费产品——删除char
public synchronized char deleteChar(int id){
//判断容器是否已耗尽
while(sb.length()==0){
try{
wait();
}catch(InterruptedException e){e.printStackTrace();}
}
char cDel = sb.charAt(0);
sb.deleteCharAt(0);
System.out.println(id+",消费了:"+cDel +" 容器:"+sb);
this.notify();
return cDel;
}
public static void main(String[] args){
ProduceAndConsumer pac = new ProduceAndConsumer();
new Thread(new AppendThread(pac)).start();
new Thread(new DeleteThread(pac)).start();
}
}
//生产线程
class AppendThread implements Runnable{
ProduceAndConsumer pac;
private char c=97;
public AppendThread(ProduceAndConsumer pac){
this.pac = pac;
}
public void run(){
for(int i=0;i<10;i++){
pac.appendChar(c++,i);
}
}
}
//消费线程
class DeleteThread implements Runnable{
ProduceAndConsumer pac;
public DeleteThread(ProduceAndConsumer pac){
this.pac = pac;
}
public void run(){
for(int i=0;i<10;i++){
pac.deleteChar(i);
}
}
}
运行结果如下(每次运行的结果可能不同),传入的参数int id只用来打印标号:
结果中,生产线程在id=4处生产e了,容器中变为abcde,容器充满(程序中设定最大长度为5),所以AppendThread线程进入沉睡状态,即进入阻塞队列。此时只运行DeleteThread消费线程。当消费线程在id=0处运行后,删除了a,容器又有了空间。此时又唤醒了AppendThread线程,即从阻塞队列进入就绪队列。由于只启动了两个线程,而且DeleteThread正在运行,所以AppendThread线程在就绪队列中不用等待,可以直接运行。后面的运行结果中,AppendThread线程和DeleteThread线程配合得当,容器中一直装有产品,且不满。两个线程都正常运行。
这样就实现了两个方法A()和B()在不同线程中的同步。