我在Java中有以下类
public class Counter {
private int value;
public Counter(int value) {
this.value = value;
}
public void setValue(int value) {
this.value = value;
}
public void decrement() {
this.value--;
}
public int getValue() {
return this.value;
}
}
public class Cell extends Thread {
private Object sync;
private Counter counter;
public Cell(Object sync, Counter counter) {
this.sync = sync;
this.counter = counter;
}
public void run() {
for (int r=0; r<Simulation.ROUND_NUM; r++) {
// do something
synchronized(counter) {
counter.decrement();
counter.notifyAll();
}
synchronized(sync) {
try {
sync.wait();
}
catch (Exception ex) {}
}
}
}
}
public class Simulation extends Thread {
public static final int THREAD_NUM = 5;
public static final int ROUND_NUM = 5;
public Object sync = new Object();
private Counter counter = new Counter(THREAD_NUM);
public void run() {
for (int i=0; i<THREAD_NUM; i++) {
Cell c = new Cell(sync,counter);
c.start();
}
for (int i=0; i<ROUND_NUM; i++) {
synchronized(counter) {
while(counter.getValue() != 0) {
try {
counter.wait();
}
catch (Exception ex) {}
}
counter.setValue(THREAD_NUM);
}
synchronized(sync) {
sync.notifyAll();
}
}
}
}
目的是防止在每个Cell Thread中执行下一个循环迭代,直到每次迭代都完成每个Cell Thread。我的解决方案有时会导致死锁。我不明白为什么。请帮忙
答案 0 :(得分:5)
首先,您可以使用AtomicInteger类而不是您制作的Counter类。 AtomicInteger类是线程安全的,因此您可以使用原子操作,例如 decrementAndGet 和 incrementAndGet 。
为了实现等待每个Cell线程完成的功能,您可以使用前一个注释中提到的CountDownLatch,或甚至像CyclicBarriers这样的并发对象来暂停执行直到所有Cell线程加入障碍。通过其中一些并发对象,控制多个线程应该更容易。使用普通同步也可以正常工作,您通常需要进行更多编码和思考以确保一切正常。
答案 1 :(得分:3)
在您的代码中,似乎无法保证在sync.notifyAll()
执行时,所有Cell线程都到达sync.wait()
。这是指最后一个Cell线程(在您的示例中为第五个)需要获取sync
的锁以便等待它。但是,在没有确保每个人都在等待的情况下,Simulation线程也在尝试同样的事情。该竞争条件使得Simulation有时会在最后一个Cell能够执行相同操作并等待之前获取锁定。
由于最后一个Cell没有等待,因此不会收到通知,因此整个事情都会被卡住。
您可以通过添加System.out.println()作为每个synchronized (sync)
块中的第一行并相应地写入“等待同步”和“通知同步”来测试此操作。您会看到通知它时只有4个线程在等待同步。
要确保每个人在模拟器通知时都在等待,请将Cell#run()
中的两个同步块嵌套:
public class Counter {
private int value;
public Counter(int value) {
this.value = value;
}
public void setValue(int value) {
this.value = value;
}
public void decrement() {
this.value--;
}
public int getValue() {
return this.value;
}
public static void main(String[] args) {
new Simulation().start();
}
}
class Cell extends Thread {
private Object sync;
private Counter counter;
public Cell(Object sync, Counter counter) {
this.sync = sync;
this.counter = counter;
}
public void run() {
for (int r = 0; r < Simulation.ROUND_NUM; r++) {
// do something
synchronized (sync) {
synchronized (counter) {
counter.decrement();
counter.notifyAll();
}
try {
sync.wait();
} catch (Exception ignored) {}
}
}
}
}
class Simulation extends Thread {
public static final int THREAD_NUM = 900;
public static final int ROUND_NUM = 30;
public Object sync = new Object();
private Counter counter = new Counter(THREAD_NUM);
public void run() {
for (int i = 0; i < THREAD_NUM; i++) {
Cell c = new Cell(sync, counter);
c.start();
}
for (int i = 0; i < ROUND_NUM; i++) {
synchronized (counter) {
while (counter.getValue() != 0) {
try {
counter.wait();
} catch (Exception ex) {
}
}
counter.setValue(THREAD_NUM);
}
synchronized (sync) {
sync.notifyAll();
}
}
}
}
答案 2 :(得分:2)
您的代码可能会死锁,因为您没有保证在发生notifyAll时Cell线程实际上会在wait()块中。以下是可能导致此问题的一系列事件:
答案 3 :(得分:0)
可爱的例子!它不是死锁,因为根据定义,只有当一个线程同时拥有多个锁时,另一个尝试以不同的顺序获取相同的锁时才会发生死锁。
我怀疑这里的问题是由Cell对象中发生的虚假唤醒引起的(如果在Simulation对象中发生虚假唤醒,它将无效,因为在循环中调用wait()会导致等待重新进入)
在Cell中的虚假唤醒将导致额外的减少。这反过来会使测试while(counter.getValue() != 0)
被逐步推翻
将该条件更改为while(counter.getValue() >= 0)
并且'死锁应该消失。如果有效,请告诉我们。
答案 4 :(得分:0)
这不是僵局。您的主线程可能会错过计数器上的通知,并且在它被读取为0后将被卡在counter.wait()上。使用jstack JDK工具来分析在这种情况下正在执行的线程。