关于多线程:Java信号量导致多线程死锁

Java Semaphore cause deadlock on multithread

使用信号量时,我应该解决多线程问题吗?
经过一段时间的测试后,即使有足够的许可,Semaphore#release也不会导致获取唤醒。

底部是我的测试代码。

  • 带有2个许可证的信号灯
  • 线程3和线程2首先
  • 线程3获得许可,等待lock,这将由线程1通知
  • 线程2获得许可,等待lock1,它将由线程3通知
  • 线程1启动,线程1和线程2的睡眠时间为30ms
  • 线程1通知lock,获取2个许可
  • 线程3唤醒,通知lock1sleep(1)睡眠1毫秒,线程2首先获得许可,释放许可
  • 线程2唤醒,获取许可,然后释放许可并释放另一个许可
  • 它将在随机迭代时导致死锁,并输出这样的日志。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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

    线程3信号量释放许可后,不会导致线程2唤醒,然后线程1和线程3永远等待获取

    bleow是我的测试代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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();
        }
    }

    这是发生死锁时我的脚下倾倒物

    enter image description here


    使用

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

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

    我将信号量与fair=false一起使用,内部将初始化extends AbstractQueuedSynchronizerNonfairSync

    以下代码导致信号量始终流FIFO获取顺序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /**
     *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);
        }
    }

    您很清楚,由于释放未指示驻留线程,因此发生了死锁。
    我发现为此报告了一些错误
    http://bugs.java.com/view_bug.do?bug_id=7011859


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

    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