Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
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) {} // Key statement 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); // Key statement 2: System.out.println(message); } }
应用程序应打印出“Mares do eat oat”。是否保证始终这样做?如果不是,为什么不是呢?更改 Sleep
的两次调用的参数会有帮助吗?你如何保证主线程可以看到 message
的所有更改?
解答:该程序几乎总是打印出“Mares do eat oat”。但是,由于 "Key statement 1" 和 "Key statment 2" 之间没有happens-before 关系,因此无法保证此结果。即使 "Key statement 1" 实际上在 "Key statement 2" 之前执行,这也是如此。记住,happens-before 关系是关于可见性,而不是序列。
有两种方法可以保证主线程可以看到 message
的所有更改:
CorrectorThread
实例的引用。然后在引用 message
之前在该实例上调用 join
message
。除非通过这些方法,否则不要引用 message
。这两种技术都建立了必要的 happens-before 关系,使 message
的更改可见。
第三种技术是简单地将 message
声明为 volatile
。这保证了对 message
(如 "Key statement 1" 中)将与 message
的任何后续读取具有 happens-before 关系(如 "Key statement 2" 中所示)。但它并不能保证 "Key statement 1" 将 literally (字面上) 发生在 "Key statement 2" 之前。它们 可能 按顺序发生,但由于调度不确定性和 sleep
的未知粒度,这不能保证。
更改两个 sleep
调用的参数也无济于事,因为这不会保证 happens-before 关系。
Drop
类。
解答:java.util.concurrent.BlockingQueue
接口定义了一个队列为空时阻塞的 get
方法,以及队列已满时阻塞的 put
方法。这些实际上是由 Drop
定义的相同操作。除了 Drop
不是队列!但是,还有另一种查看 Drop 的方法:它是一个容量为零的队列。由于 any (任何) 元素在队列中都没有空间,每个 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("MESSAGE RECEIVED: %s%n", message); Thread.sleep(random.nextInt(5000)); } } catch (InterruptedException e) {} } }
ProducerConsumerExample
,我们只需更改 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(); } }