这些Java教程是针对JDK 8编写的。本页面中描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
请查看Java语言变更,了解Java SE 9及后续版本中更新的语言特性的摘要。
请查看JDK发行说明,了解所有JDK版本中的新功能、增强功能以及已删除或已弃用选项的信息。
线程经常需要协调它们的动作。最常见的协调方式是保护块。这样的块在开始时通过轮询条件来判断块是否可以继续执行。为了正确地执行这个过程,需要遵循一些步骤。
举个例子,假设guardedJoy是一个方法,在另一个线程设置了共享变量joy之前不能继续执行。理论上,这个方法可以简单地循环等待条件满足,但是这个循环是浪费的,因为它在等待期间会持续执行。
public void guardedJoy() {
// 简单的循环保护。浪费处理器时间。不要这样做!
while(!joy) {}
System.out.println("已经获得了快乐!");
}
一个更高效的保护方式是使用Object.wait来挂起当前线程。调用wait不会返回,直到另一个线程发出通知,表示可能发生了某个特殊事件 - 尽管不一定是当前线程正在等待的事件:
public synchronized void guardedJoy() {
// 这个保护只对每个特殊事件循环一次,可能不是我们正在等待的事件。
while(!joy) {
try {
wait();
} catch (InterruptedException e) {}
}
System.out.println("已经同时实现了快乐和效率!");
}
wait。不要假设中断是为了你正在等待的特定条件,或者条件仍然为真。
和许多暂停执行的方法一样,wait可能会抛出InterruptedException。在这个例子中,我们可以忽略这个异常 - 我们只关心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;
// 如果消费者应等待生产者发送消息,则为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;
// 存储消息。
this.message = message;
// 通知消费者状态已改变。
notifyAll();
}
}
生产者线程在中定义,发送一系列熟悉的消息。字符串"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[] = {
"马吃燕麦",
"母鹿吃燕麦",
"小羊吃常春藤",
"小孩也会吃常春藤"
};
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");
}
}
消费者线程,在 中定义,简单地获取消息并将其打印出来,直到获取到 "DONE" 字符串。该线程还会随机暂停一段时间。Consumer
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("接收到消息: %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();
}
}