Java 信号量导致多线程死锁

Java Semaphore cause deadlock on multithread

使用 Semaphore 时需要注意多线程问题吗? 经过我的测试似乎有一段时间 Semaphore#release 不会导致获取唤醒,即使有足够的许可。

下面是我的测试代码。

  1. 有 2 个许可的信号量
  2. 线程 3 和线程 2 先启动
  3. thread-3 获得许可,等待 lock thread-1
  4. 通知
  5. thread-2 获得许可,等待 lock1 thread-3
  6. 通知
  7. 线程1启动,线程1休眠30ms,线程2先启动
  8. 线程 1 通知 lock获得 2 个许可
  9. thread-3 唤醒,通知lock1sleep(1) sleep 1 ms for thread-2 firstrelease许可证
  10. thread-2 唤醒,获得一个许可然后释放一个许可并释放另一个许可

随机迭代会造成死锁,这样输出一些日志。

in 3, a = 2
in 2 ,a = 2
in in 2 lock 1, a = 0
in 1 , a = 0
acquire and release 3
in in 2 locked, a = 0
out 3 ,a  = 0
in 1 locked, a = 0
acquire and release 2
out 2
out 1 ,a = 2
--------------------------------------------------------------  0
in 2 ,a = 2
in 3, a = 1
in 1 , a = 0
in in 2 lock 1, a = 0
acquire and release 3
out 3 ,a  = 1 
//deadlock here

thread-3 Semaphore release permit后,不会引起thread-2唤醒,然后thread-1和thread-3永远等待acquire

下面是我的测试代码

import java.util.concurrent.Semaphore;

/**
 * Created by rqg on 6/10/17.
 */
public class WaitTest {


    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(2);

        final Object lock = new Object();
        final Object lock1 = new Object();
//        testSemaphore(semaphore, lock, lock1);

        for (int i = 0; i < 10000; i++) {
            testSemaphore(semaphore, lock, lock1);
            System.out.println("---------------------------------------------------------------------------------  " + i);
        }
    }

    private static void testSemaphore(Semaphore semaphore, Object lock, Object lock1) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(30);

                    synchronized (lock) {
                        lock.notify();
                    }
                    System.out.println("in 1 , a = " + semaphore.availablePermits());
                    semaphore.acquire(2);
                    System.out.println("in 1 locked, a = " + semaphore.availablePermits());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                semaphore.release(2);

                System.out.println("out 1 ,a = " + semaphore.availablePermits());
            }
        };


        Thread t2 = new Thread() {
            @Override
            public void run() {
                try {

                    System.out.println("in 2 ,a = " + semaphore.availablePermits());
                    semaphore.acquire();

                    synchronized (lock1) {
                        lock1.wait();
                    }

                    System.out.println("in in 2 lock 1, a = " + semaphore.availablePermits());
                    semaphore.acquire();
                    System.out.println("in in 2 locked, a = " + semaphore.availablePermits());
                    semaphore.release();

                    semaphore.release();

                    System.out.println("acquire and release 2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("out 2");
            }
        };

        Thread t3 = new Thread() {
            @Override
            public void run() {
                try {
                    System.out.println("in 3, a = " + semaphore.availablePermits());
                    semaphore.acquire();

                    synchronized (lock) {
                        lock.wait();
                    }

                    synchronized (lock1) {
                        lock1.notify();
                    }
                    sleep(1);

                    semaphore.release();

                    System.out.println("acquire and release 3");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("out 3 ,a  = " + semaphore.availablePermits());

            }
        };

        t1.start();
        t2.start();
        t3.start();


        t1.join();
        t2.join();
        t3.join();
    }
}

这是发生死锁时我的胎面转储

信号量只保留许可数。来自信号量文档:

Conceptually, a semaphore maintains a set of permits. Each {@link acquire} blocks if necessary until a permit is available, and then takes it. Each {@link #release} adds a permit, potentially releasing a blocking acquirer. However, no actual permit objects are used; the {@code Semaphore} just keeps a count of the number available and acts accordingly.

我的意思是程序应该注意同步。

Semaphores are often used to restrict the number of threads than can access some (physical or logical) resource

你认为死锁发生是因为释放没有发出停放线程的信号。 我发现了一些为此报告的错误 http://bugs.java.com/view_bug.do?bug_id=7011859

java.util.concurrent.locks.AbstractQueuedSynchronizerSemaphore 用来做一些同步工作。 它有 FIFO 属性,这会导致问题。

我有2个许可,thread-3释放一个许可后,只有一个许可可用,根据Semaphore FIFO获取顺序,如果我的thread-3 acquire(2)发生在[=15=之前], 线程 3 将永远阻塞。

我将 Semaphore 与 fair=false 一起使用,内部将初始化 NonfairSync 其中 extends AbstractQueuedSynchronizer.

下面的代码导致信号量总是流 FIFO 获取顺序。

/**
 *java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly
*/
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}