| 
                             
保护块   
    
 
多线程之间经常需要协同工作,最常见的方式是使用保护块(Guarded Blocks),它循环检查一个条件(通常初始值为true),直到条件发生变化才跳出循环继续执行。在使用Guarded Blocks时有以下几个步骤需要注意:  
假设guardedJoy()方法必须要等待另一线程为共享变量joy设值才能继续执行。那么理论上可以用一个简单的条件循环来实现,但在等待过程中guardedJoy方法不停的检查循环条件实际上是一种资源浪费。  
 
public void guardedJoy() {
    // Simple loop guard. Wastes
    // processor time. Don't do this!
    while(!joy) {}
    System.out.println("Joy has been achieved!");
} 
更加高效的方法是调用Object.wait将当前线程挂起,直到有另一线程发起事件通知(尽管通知的事件不一定是当前线程等待的事件)。    
 
public synchronized void guardedJoy() {
    // This guard only loops once for each special event,which may not
    // be the event we're waiting for.
    while(!joy) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
} 
 
注意:一定要在循环里面调用wait方法,不要想当然的认为线程唤醒后循环条件一定发生了改变。(译注:这样可以保证若循环条件没改变,当前线程将重新挂起。)  
和其他可以暂停线程执行的方法一样,wait方法会抛出InterruptedException,在上面的例子中,因为我们关心的是joy的值,所以忽略了InterruptedException。  
为什么guardedJoy是synchronized方法?假设d是用来调用wait的对象,当一个线程调用d.wait,它必须要拥有d的内部锁(否则会抛出异常),获得d的内部锁的最简单方法是在一个synchronized方法里面调用wait。  
当一个线程调用wait方法时,它释放锁并挂起。然后另一个线程请求并获得这个锁并调用Object.notifyAll通知所有等待该锁的线程。  
public synchronized notifyJoy() {
    joy = true;
    notifyAll();
} 
当第二个线程释放这个该锁后,第一个线程再次请求该锁,从wait方法返回并继续执行。  
注意:还有另外一个通知方法,notify(),它只会唤醒一个线程。但由于它并不允许指定哪一个线程被唤醒,所以一般只在大规模并发应用(即系统有大量相似任务的线程)中使用。因为对于大规模并发应用,我们其实并不关心哪一个线程被唤醒。  
现在我们使用Guarded blocks创建一个生产者/消费者应用。这类应用需要在两个线程之间共享数据:生产者生产数据,消费者使用数据。两个线程通过共享对象通信。在这里,线程协同工作的关键是:生产者发布数据之前,消费者不能够去读取数据;消费者没有读取旧数据前,生产者不能发布新数据。  
在下面的例子中,数据是一系列文本消息,通过Drop类型的对象来共享:  
public class Drop {
    // Message sent from producer to consumer.
    private String message;
    // True if consumer should wait for producer to send message,// false if producer should wait for consumer to retrieve message.
    private boolean empty = true;
    public synchronized String take() {
        // Wait until message is available.
        while (empty) {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        // Toggle status.
        empty = true;
        // Notify producer that status has changed.
        notifyAll();
        return message;
    }
    public synchronized void put(String message) {
        // Wait until message has been retrieved.
        while (!empty) {
            try { 
                wait();
            } catch (InterruptedException e) {}
        }
        // Toggle status.
        empty = false;
        // Store message.
        this.message = message;
        // Notify consumer that status has changed.
        notifyAll();
    }
} 
Producer是生产者线程,发送一组消息,字符串DONE表示所有消息都已经发送完成。为了模拟现实情况,生产者线程还会在消息发送时随机的暂停。 
 
import java.util.Random;
public class Producer implements Runnable {
    private Drop drop;
    public Producer(Drop drop) {
        this.drop = drop;
    }
    public void run() {
        String importantInfo[] = {
            "Mares eat oats","Does eat oats","Little lambs eat ivy","A kid will eat ivy too"
        };
        Random random = new Random();
        for (int i = 0; i < importantInfo.length; i++) {
            drop.put(importantInfo[i]);
            try {
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {}
        }
        drop.put("DONE");
    }
} 
Consumer是消费者线程,读取消息并打印出来,直到读取到字符串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"); message = drop.take()) {
            System.out.format("MESSAGE RECEIVED: %s%n",message);
            try {
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {}
        }
    }
}下面是主线程 
ProducerConsumerExample,用于启动Producer和Consumer线程。 
  
 
 
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类是用来演示Guarded Blocks如何工作的。为了避免重新发明轮子,当你尝试创建自己的共享数据对象时,请查看 
Java Collections Framework中已有的数据结构。如需更多信息,请参考 
Questions and Exercises。 
  
    
不可变对象 
    
一个对象如果在创建后不能被修改,那么就称为不可变对象。在并发编程中,一种被普遍认可的原则就是:尽可能的使用不可变对象来创建简单、可靠的代码。  
在并发编程中,不可变对象特别有用。由于创建后不能被修改,所以不会出现由于线程干扰产生的错误或是内存一致性错误。  但是程序员们通常并不热衷于使用不可变对象,因为他们担心每次创建新对象的开销。实际上这种开销常常被过分高估,而且使用不可变对象所带来的一些效率提升也抵消了这种开销。例如:使用不可变对象降低了垃圾回收所产生的额外开销,也减少了用来确保使用可变对象不出现并发错误的一些额外代码。  接下来看一个可变对象的类,然后转化为一个不可变对象的类。通过这个例子说明转化的原则以及使用不可变对象的好处。  
一个同步类的例子 
SynchronizedRGB是表示颜色的类,每一个对象代表一种颜色,使用三个整形数表示颜色的三基色,字符串表示颜色名称。 
  
public class SynchronizedRGB {
    // Values must be between 0 and 255.
    private int red;
    private int green;
    private int blue;
    private String name;
    private void check(int red,int green,int blue) {
        if (red < 0 || red > 255 ||
            green < 0 || green > 255 ||
            blue < 0 || blue > 255) {
            throw new IllegalArgumentException();
        }
    }
    public SynchronizedRGB(int red,int blue,String name) {
        check(red,green,blue);
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.name = name;
    }
    public void set(int red,blue);
        synchronized (this) {
            this.red = red;
            this.green = green;
            this.blue = blue;
            this.name = name;
        }
    }
    public synchronized int getRGB() {
        return ((red << 16) | (green << 8) | blue);
    }
    public synchronized String getName() {
        return name;
    }
    public synchronized void invert() {
        red = 255 - red;
        green = 255 - green;
        blue = 255 - blue;
        name = "Inverse of " + name;
    }
} 
使用SynchronizedRGB时需要小心,避免其处于不一致的状态。例如一个线程执行了以下代码:  
 
SynchronizedRGB color = new SynchronizedRGB(0,"Pitch Black");
...
int myColorInt = color.getRGB();      //Statement 1
String myColorName = color.getName(); //Statement 2 如果有另外一个线程在Statement 1之后、Statement 2之前调用了color.set方法,那么myColorInt的值和myColorName的值就会不匹配。为了避免出现这样的结果,必须要像下面这样把这两条语句绑定到一块执行: 
  
synchronized (color) {
    int myColorInt = color.getRGB();
    String myColorName = color.getName();
} 
 
这种不一致的问题只可能发生在可变对象上。  
定义不可变对象的策略 
                        (编辑:莱芜站长网) 
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! 
                     |