java-并发-保护代码块
浏览以下内容前,请点击并阅读 声明
线程经常需要协调其动作,最常用的协调方法就是保护代码块,该代码块以一个条件判断开始,当判断为true时才能开始执行。
假设一个方法guradedJoy必须等到变量joy由其他线程设定过后才能开始执行,这样的方法,理论上简单的循环直到满足条件就可以了,但是这样的循环是比较浪费资源的,因为等待的时候是在不停的执行的。
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 e) {} } System.out.println("Joy and efficiency have been achieved!"); }
注意:在一个测试一个等待条件的对于你所等待的条件的,或者等待的条件依然为true。
像其他暂停执行的方法一样,wait方法可以抛出InterruptException,当一个线程调用一个对象的wait方法时,该线程必须拥有该对象的内置锁(如上例中guardedJoy方法为同步方法),否则将抛出错误,在同步方法中调用wait方法是一个请求内置锁的简单途径。
当调用wait方法时,线程释放锁并暂停执行。在接下来的时间里,另外一个线程将请求同一个锁并调用对象的notifyAll,通知等待该锁的所有线程一些已经发生的重要事情。当第二个线程释放了锁之后,第一个线程就会请求该锁并从调用的wait方法中返回。
注意:还有另外一个通知的方法,notify,该方法只唤醒一个线程,因为notify方法无法指定要被唤醒的线程,这个方法只在大规模并行程序中有用,也就是说,程序中有许多线程做相同的事情,这样的程序中,不必在意唤醒的是哪个线程。
以下利用保护代码块来创建一个简单的生产者-消费者程序,给程序在两个线程之间分享数据,生产者产生数据,而消费者对数据进行处理,两个线程之间通过一个共享的对象进行通信。线程之间的协调是很重要的:消费者在生产者传递数据之间不能检索数据,而生产者在旧数据被消费者检索之前不能传递新的数据。
以下定义一个类用来创建通信的对象:
public class Drop { // 生产者发给消费者的信息 private String message; // 如果消费者需要等待消费者发送信息则为true,如果需要等待消费者接受信息则为false private boolean empty = true; public synchronized String take() { // 等待信息可用 while (empty) { try { wait(); } catch (InterruptedException e) {} } // 转换状态 empty = true; // 通知生产者信息已被检索 notifyAll(); return message; } public synchronized void put(String message) { // 等待信息被检索 while (!empty) { try { wait(); } catch (InterruptedException e) {} } // 转换状态 empty = false; // Store message. this.message = message; // 通知消费者状态已经改变 notifyAll(); } }
定义一个Producer类,发送一系列熟悉的信息,字符串“DONE”表明所有的信息已经被发送,为模拟真实的程序,发送信息的间歇Producer类会停顿随机的一段时间。
//导入随机类,以产生一个随机数 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
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的集合框架(Java Collections Framework)提供了数据分享的类,提供丰富的功能,因此可以不必定义自己的类。