防止双线程程序中的死锁
Preventing deadlock in two thread program
假设我有以下代码,其中一个线程生成正方形并将它们写入缓冲区,而另一个线程打印它们:
import java.util.*;
public class Something {
public static Buffer buffer = new Buffer();
public static class Buffer {
private int[] buffer;
private static final int size = 10;
//Indexes for putting and taking element form buffer
private int in, out;
//Number of elements in buffer
private int k;
public Buffer() {
buffer = new int[size];
in = 0;
out = 0;
k = 0;
}
public synchronized void put(int e) {
try {
while (k == buffer.length) {
wait();
}
} catch (InterruptedException ex) {
}
buffer[in] = e;
k++;
in = ++in % size;
notifyAll();
}
public synchronized int take() {
try {
while (k == 0) {
wait();
}
} catch (InterruptedException ex) {
}
int e = buffer[out];
buffer[out] = 0;
out = ++out % size;
k--;
notifyAll();
return e;
}
public synchronized boolean notEmpty() {
return k != 0;
}
}
public static class Generator implements Runnable {
int limit;
public Generator(int lim) {
limit= lim;
}
@Override
public void run() {
for (int i = 1; i < limit; i++) {
buffer.put(i * i);
}
}
}
public static class Printer implements Runnable {
private Thread[] generators;
public Printer(Thread[] gen) {
generators = gen;
}
public synchronized boolean nobody() {
for (Thread th : generators) {
if (th.isAlive()) {
return false;
}
}
return true;
}
@Override
public void run() {
int x = 0;
while (!nobody() || buffer.notEmpty()) {
x = buffer.take();
System.out.println(x);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread generator = new Thread(new Generator(69));
Thread printer = new Thread(new Printer(new Thread[]{generator}));
generator.start();
printer.start();
generator.join();
printer.join();
}
}
生成器应生成数字的平方,直到达到某个限制(在本例中为 limit = 69)。打印机应打印生成器生成的所有值。缓冲区的工作方式有点像环形缓冲区。用于放置 (in) 和获取 (out) 元素的索引在缓冲区大小的范围内循环。 Buffer 具有从缓冲区中放入和取出元素的方法。如果缓冲区已满,则生成器线程无法将元素放入缓冲区(即,没有零元素;为了精确起见,零元素为 0 ...)。打印机以这种方式工作:首先它检查是否有任何活动的生成器线程,然后检查缓冲区是否仅包含零元素。如果这些条件都不成立,打印机线程将终止。
现在,进入正题。我总是打印从 1 到 68 的所有方块,这是该程序的预期输出。 但是,在非常罕见的情况下,在输出所有数字后我会遇到死锁。很少见?好吧,也许在 100 次程序执行中有 1 次。我不得不疯狂地在 NetBeans 上不断点击 "F6" 才能陷入僵局。是的,我知道我可以简单地将所有主要代码放入 for 循环中进行测试。
相反,如果我在 Printers 的 运行 方法中注释掉打印行,死锁几乎总是发生。这里:
@Override
public void run() {
int x = 0;
while (!nobody() || buffer.notEmpty()) {
x = buffer.take();
//System.out.println(x);
}
}
我不希望出现这种行为,因为元素仍会从缓冲区中取出,生成器应该被唤醒。
为什么会这样?我该如何解决?
抱歉,如果问题不够清楚,如果需要,我会尽力澄清。
我想我找到了问题所在。这是我得到的:有一个非常短的时刻,Generator
线程仍然存在(即 Thread.isAlive()
将 return true
),但是 Generator
已离开 run()
内的 for
循环。如果 Printer
此时在其 run()
内查询其 while
条件,它将尝试 take()
某些不存在(并且永远不会存在)的东西.事实上,您可以验证 Generator
总是完成,这意味着 Printer
端的终止检测是错误的。对于热修复,您可以简单地添加一个魔法常量 is Printer
s while condition:
@Override
public void run() {
int x = 0;
int count = 0;
while (++count < 69) {
x = buffer.take();
System.out.println(x);
}
}
对于干净的终止检测,您可以将一些常见的标志变量设置为 false
,表示 Generator
已完成工作并且 Printer
可以停止工作。但这必须以同步方式完成,这意味着 Printer
不允许查询此条件,而 Generator
在其最后一个 push
之后,但在设置此公共标志之前.
假设我有以下代码,其中一个线程生成正方形并将它们写入缓冲区,而另一个线程打印它们:
import java.util.*;
public class Something {
public static Buffer buffer = new Buffer();
public static class Buffer {
private int[] buffer;
private static final int size = 10;
//Indexes for putting and taking element form buffer
private int in, out;
//Number of elements in buffer
private int k;
public Buffer() {
buffer = new int[size];
in = 0;
out = 0;
k = 0;
}
public synchronized void put(int e) {
try {
while (k == buffer.length) {
wait();
}
} catch (InterruptedException ex) {
}
buffer[in] = e;
k++;
in = ++in % size;
notifyAll();
}
public synchronized int take() {
try {
while (k == 0) {
wait();
}
} catch (InterruptedException ex) {
}
int e = buffer[out];
buffer[out] = 0;
out = ++out % size;
k--;
notifyAll();
return e;
}
public synchronized boolean notEmpty() {
return k != 0;
}
}
public static class Generator implements Runnable {
int limit;
public Generator(int lim) {
limit= lim;
}
@Override
public void run() {
for (int i = 1; i < limit; i++) {
buffer.put(i * i);
}
}
}
public static class Printer implements Runnable {
private Thread[] generators;
public Printer(Thread[] gen) {
generators = gen;
}
public synchronized boolean nobody() {
for (Thread th : generators) {
if (th.isAlive()) {
return false;
}
}
return true;
}
@Override
public void run() {
int x = 0;
while (!nobody() || buffer.notEmpty()) {
x = buffer.take();
System.out.println(x);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread generator = new Thread(new Generator(69));
Thread printer = new Thread(new Printer(new Thread[]{generator}));
generator.start();
printer.start();
generator.join();
printer.join();
}
}
生成器应生成数字的平方,直到达到某个限制(在本例中为 limit = 69)。打印机应打印生成器生成的所有值。缓冲区的工作方式有点像环形缓冲区。用于放置 (in) 和获取 (out) 元素的索引在缓冲区大小的范围内循环。 Buffer 具有从缓冲区中放入和取出元素的方法。如果缓冲区已满,则生成器线程无法将元素放入缓冲区(即,没有零元素;为了精确起见,零元素为 0 ...)。打印机以这种方式工作:首先它检查是否有任何活动的生成器线程,然后检查缓冲区是否仅包含零元素。如果这些条件都不成立,打印机线程将终止。
现在,进入正题。我总是打印从 1 到 68 的所有方块,这是该程序的预期输出。 但是,在非常罕见的情况下,在输出所有数字后我会遇到死锁。很少见?好吧,也许在 100 次程序执行中有 1 次。我不得不疯狂地在 NetBeans 上不断点击 "F6" 才能陷入僵局。是的,我知道我可以简单地将所有主要代码放入 for 循环中进行测试。 相反,如果我在 Printers 的 运行 方法中注释掉打印行,死锁几乎总是发生。这里:
@Override
public void run() {
int x = 0;
while (!nobody() || buffer.notEmpty()) {
x = buffer.take();
//System.out.println(x);
}
}
我不希望出现这种行为,因为元素仍会从缓冲区中取出,生成器应该被唤醒。
为什么会这样?我该如何解决? 抱歉,如果问题不够清楚,如果需要,我会尽力澄清。
我想我找到了问题所在。这是我得到的:有一个非常短的时刻,Generator
线程仍然存在(即 Thread.isAlive()
将 return true
),但是 Generator
已离开 run()
内的 for
循环。如果 Printer
此时在其 run()
内查询其 while
条件,它将尝试 take()
某些不存在(并且永远不会存在)的东西.事实上,您可以验证 Generator
总是完成,这意味着 Printer
端的终止检测是错误的。对于热修复,您可以简单地添加一个魔法常量 is Printer
s while condition:
@Override
public void run() {
int x = 0;
int count = 0;
while (++count < 69) {
x = buffer.take();
System.out.println(x);
}
}
对于干净的终止检测,您可以将一些常见的标志变量设置为 false
,表示 Generator
已完成工作并且 Printer
可以停止工作。但这必须以同步方式完成,这意味着 Printer
不允许查询此条件,而 Generator
在其最后一个 push
之后,但在设置此公共标志之前.