Java教程是为JDK 8编写的。本页面中描述的示例和实践不利用后续版本中引入的改进,并可能使用不再可用的技术。
有关Java SE 9及其后续版本中更新的语言功能的摘要,请参阅Java语言变更。
有关所有JDK版本的新功能、增强功能以及删除或弃用选项的信息,请参阅JDK发布说明。
Thread
对象传递给Executor.execute
吗?这样的调用是否有意义?为什么有或者为什么没有?
回答:Thread
实现了Runnable
接口,所以你可以将Thread
的实例传递给Executor.execute
。然而,以这种方式使用Thread
对象并没有意义。如果该对象直接从Thread
实例化,它的run
方法不会做任何事情。你可以定义一个有用的run
方法的Thread
子类 - 但是这样的类将实现执行器不会使用的特性。
BadThreads.java
:
public class BadThreads { static String message; private static class CorrectorThread extends Thread { public void run() { try { sleep(1000); } catch (InterruptedException e) {} // 关键语句 1: message = "Mares do eat oats."; } } public static void main(String args[]) throws InterruptedException { (new CorrectorThread()).start(); message = "Mares do not eat oats."; Thread.sleep(2000); // 关键语句 2: System.out.println(message); } }
该应用程序应该打印出 "Mares do eat oats." 这句话。它能保证总是打印出这个结果吗?如果不能,为什么?改变两个Sleep
的参数会有帮助吗?如何确保主线程能看到对message
的所有更改?
解决方案: 该程序几乎总是会打印出 "Mares do eat oats." 这句话。然而,这个结果并不能保证,因为"关键语句 1"和"关键语句 2"之间没有发生在之前的关系。即使"关键语句 1"实际上在"关键语句 2"之前执行,这个发生在之前的关系是关于可见性的,而不是顺序。
有两种方法可以确保主线程能看到对message
的所有更改:
CorrectorThread
实例的引用。然后在引用message
之前,在该实例上调用join
message
在一个对象中。除了通过这些方法之外,不要直接引用message
这两种技术都建立了必要的发生在之前的关系,使对message
的更改可见。
第三种技术是将message
声明为volatile
。这保证了对message
的任何写入(如"关键语句 1")与任何后续对message
的读取(如"关键语句 2")之间存在发生在之前的关系。但它不能保证"关键语句 1"会确切地在"关键语句 2"之前发生。它们可能按顺序发生,但由于调度的不确定性和sleep
的未知粒度,这并不是保证。
改变两个sleep
调用的参数也无济于事,因为这对于确保发生在之前的关系没有任何帮助。
Drop
解决方案: java.util.concurrent.BlockingQueue
接口定义了一个 get
方法,如果队列为空,则会阻塞,还定义了一个 put
方法,如果队列已满,则会阻塞。这些操作与 Drop
的操作基本相同 - 只是 Drop
不是一个队列!不过,还有另一种看待 Drop 的方式:它是一个容量为零的队列。由于队列中没有空间来存储 任何 元素,每个 get
操作都会阻塞,直到对应的 take
操作,每个 take
操作都会阻塞,直到对应的 get
操作。有一种实现了这种行为的 BlockingQueue
: java.util.concurrent.SynchronousQueue
。
BlockingQueue
几乎可以无缝替换 Drop
。在
中,主要的问题是使用 Producer
BlockingQueue
时,put
和 get
方法会抛出 InterruptedException
异常。这意味着现有的 try
必须向上移动一级:
import java.util.Random; import java.util.concurrent.BlockingQueue; public class Producer implements Runnable { private BlockingQueue<String> drop; public Producer(BlockingQueue<String> 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(); try { for (int i = 0; i < importantInfo.length; i++) { drop.put(importantInfo[i]); Thread.sleep(random.nextInt(5000)); } drop.put("DONE"); } catch (InterruptedException e) {} } }
Consumer
import java.util.Random; import java.util.concurrent.BlockingQueue; public class Consumer implements Runnable { private BlockingQueue<String> drop; public Consumer(BlockingQueue<String> drop) { this.drop = drop; } public void run() { Random random = new Random(); try { for (String message = drop.take(); ! message.equals("DONE"); message = drop.take()) { System.out.format("接收到的消息: %s%n", message); Thread.sleep(random.nextInt(5000)); } } catch (InterruptedException e) {} } }
生产者消费者示例
drop
import java.util.concurrent.BlockingQueue; import java.util.concurrent.SynchronousQueue; public class ProducerConsumerExample { public static void main(String[] args) { BlockingQueue<String> drop = new SynchronousQueue<String> (); (new Thread(new Producer(drop))).start(); (new Thread(new Consumer(drop))).start(); } }