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 之前在该实例上调用 joinmessage。除非通过这些方法,否则不要引用 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 的直接替代品。 中使用 ProducerBlockingQueue 的主要问题是,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();
}
}