循环同步死锁

时间:2011-01-21 22:12:40

标签: java concurrency thread-safety deadlock

我在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。我的解决方案有时会导致死锁。我不明白为什么。请帮忙

5 个答案:

答案 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()块中。以下是可能导致此问题的一系列事件:

  1. 模拟启动所有线程,并阻塞等待0值。
  2. 序列中的每个线程调用递减,然后是counter.notifyAll,然后丢失其时间片
  3. 主线程已被通知,唤醒,发现计数器为0,调用sync.notifyAll,循环到顶部,并无限期等待。
  4. 序列中的每个线程都有一个时间片,前进到wait(),并无限期地等待。

答案 3 :(得分:0)

可爱的例子!它不是死锁,因为根据定义,只有当一个线程同时拥有多个锁时,另一个尝试以不同的顺序获取相同的锁时才会发生死锁。
我怀疑这里的问题是由Cell对象中发生的虚假唤醒引起的(如果在Simulation对象中发生虚假唤醒,它将无效,因为在循环中调用wait()会导致等待重新进入) 在Cell中的虚假唤醒将导致额外的减少。这反过来会使测试while(counter.getValue() != 0)被逐步推翻 将该条件更改为while(counter.getValue() >= 0)并且'死锁应该消失。如果有效,请告诉我们。

答案 4 :(得分:0)

这不是僵局。您的主线程可能会错过计数器上的通知,并且在它被读取为0后将被卡在counter.wait()上。使用jstack JDK工具来分析在这种情况下正在执行的线程。