线程经常要调整自己的行为,最常见的调整方式是保护块,这种保护块通过轮询一个必须返回true的条件开始,然后才能继续处理。为了保证这个的正确性,依赖于一系列的步骤:
假设,guardedJoy方法直到一个共享变量被另外一个线程设置为true后才能够继续运行,理论上,这种方法会一直循环直到条件满足,但是这种循环会很浪费,因为在等待的时候它会连续不断的执行。
public void guardedJoy(){
while(!joy){}
System.out.println("Joy has been achieved!");
}
一个更有效的保护是调用object对象wait方法挂起线程,调用wait方法不会返回直到另外一个线程通知它,有特别的事件发生,尽管不是该线程正在等待的事情:
public synchronized void guardedJoy(){
while(!joy){
try{
wait(); }catch(InterruptedException){
}
System.out.println("oy and efficiency have been achieved!");
}
}
注意: 在一个循环中检测等待的条件是否满足,调用wait方法,不要认为中断就是你等待的条件满足了,或者条件仍然是true。
类似挂起执行的很多方法,wait会抛出中断异常,这个例子中,我们可以忽略那异常,我们只关心joy的值。
为什么这个版本的guardedJoy方法同步了?假设用我们正在使用的对象 d 来调用它的wait方法,当一个线程调用d.wait, 它必须拥有对象d的内在锁,否则,错误将会被抛出,在一个同步方法中调用wait方法是一种简单获取内在锁的方式。
当wait方法被调用,线程释放锁同时执行挂起,在接下来的一段时间内,其它的线程将会获得同样的锁并调用Object.notifyAll,告知所有正在等待锁的线程,发生了一些重要的事情 :
public synchronized notifyJoy(){
joy = true;
notifyAll();
}
某些时候,当第二个线程释放掉锁,第一个线程重新获得这锁并从调用wait方法的地方恢复运行。
注意: 有另外一个通知方法,notify,这会仅仅唤醒一个线程,因为notify不允许你唤醒指定的线程,它仅仅在大量并发的应用中有用,也就是,大量线程做相同的事情的时候。在这样一个应用中,你不需要关心哪个线程会被唤醒。
让我们使用保护块来创建一个生产者-消费者应用。这种应用在两个线程中共享数据,一个创造数据的生产者,一个使用数据的消费者。这两个线程通过共享对象来通信。协调合作是很重要的:一个消费者不能试图获取生产者还没传送过来的数据,生产者不能在消费者没有获取之前数据的时候给消费者再发送数据。
以下例子中,数据是一系列的文本消息,通过一个Drop类型的对象共享:
public class Drop{
private String message;
private boolean empty = true;
public synchronized String take(){
while(empty){
try{
wait();
}catch(InterruptedException e){
}
}
enpty = true;
notifyAll();
return message;
}
public synchronized void put(String message){
while(!empty){
try{
wait();
}catch(InterruptedException e){
}
}
empty = false;
this.message = message;
notifyAll();
}
}
生产者线程发送一系列相同的信息,字符串“DONE”表明所有的信息已经被发送出去,为了模仿现实世界应用的不可预期性,生产者在消息之间随机间隔暂停。
import java.util.Random;
public class Producer implements Runnable{
private Drop drop;
public Producer(Drop drop){
this.drop = drop;
}
public void run(){
String info[] = {
"aaa",
"bbb",
"ccc"
};
Random random = new Random();
for(int i = 0;i<info.length;i++){
drop.put(info[i]);
try{
Thread.sleep(random.nextInt(500));
}catch(InterruptedException e){
}
}
drop.put("DONE");
}
}
消费者线程简单的获取消息并打印出来,直到获取“DONE”字符串,这个线程也是随机间隔暂停。
import java.util.Random;
public class Consumer implements Runnable{
private Drop drop;
public Consumer(Drop drop){
this.drop = drop;
}
public void run(){
Random random = new Random();
for(String message = drop.take();!message.equals("DONE")){
try{
Thread.sleep(random.nextInt(5000));
}catch(InterruptedException e){}
}
}
}
最后,主线程,定义为ProducerConsumerExample,启动生产和消费线程。
public class ProducerConsumerExample{
public static void main(String[] args){
Drop drop = new Drop();
(new Thread(new Producer(drop))).start();
(new Thread(new Consumer(drop))).start();
}
}
注意: Drop类是为了演示保护块而设计的一个类,为了避免重复造轮子,测试java集合框架中已经存在的数据结果作为例子。为了获得更多的消息,请跳转至http://docs.oracle.com/javase/tutorial/essential/concurrency/QandE/questions.html章节