保护块(Guarded Blocks)

   线程经常要调整自己的行为,最常见的调整方式是保护块,这种保护块通过轮询一个必须返回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章节

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值