【问题标题】:Java Semaphore cause deadlock on multithreadJava Semaphore 导致多线程死锁
【发布时间】:2017-06-13 05:05:00
【问题描述】:

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

底部是我的测试代码。

  1. 具有 2 个许可证的信号量
  2. 线程 3 和线程 2 先上
  3. thread-3 获得许可,等待lock 将由 thread-1 通知
  4. thread-2 获得许可,等待 lock1 将由 thread-3 通知
  5. 线程 1 启动,休眠 30ms 线程 1 和线程 2 先启动
  6. thread-1 通知lock获取 2 个许可
  7. 线程 3 唤醒,通知 lock1sleep(1) 休眠 1 毫秒,线程 2 先获取许可释放许可
  8. 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

bleow 是我的测试代码

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();
    }
}

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

【问题讨论】:

    标签: java multithreading deadlock semaphore


    【解决方案1】:

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

    从概念上讲,信号量维护一组许可。每个{@link 如果有必要,acquire} 会阻塞,直到获得许可,然后再接受它。每个 {@link #release} 添加一个许可,可能 释放阻塞的收单方。但是,没有实际的许可对象 被使用; {@code Semaphore} 只记录数字 可用并采取相应措施。

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

    信号量通常用于限制线程数 访问一些(物理或逻辑)资源

    【讨论】:

    • 信号量的方法不同步
    • 如果 Semaphore 不是线程安全的,那么 Semaphore 是没用的。我不明白为什么 Semaphore 不是线程安全的并且是为并发用例设计的
    • 如果没有可用的许可,信号量会阻塞。我认为存在死锁,因为有时没有线程可以释放许可证。
    • release permit 会唤醒阻塞的线程,作为我的日志,我有一个可用的 permit,但是 thread-2 没有唤醒。
    【解决方案2】:

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

    我有2个permit,thread-3释放一个permit后,只有一个permit可用,根据SemaphoreFIFO获取顺序,如果我的thread-3 acquire(2)发生在thread-2 acquire(1)之前,thread-3将永远阻塞.

    我将信号量与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);
        }
    }
    

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 我更新了我的问题,并在死锁后添加了线程转储。 thread_2thread_1 等待 Semaphore#acquire 。 lock 不会导致死锁问题。
      猜你喜欢
      • 1970-01-01
      • 2018-11-09
      • 1970-01-01
      • 2011-07-01
      • 1970-01-01
      • 2021-03-14
      • 1970-01-01
      • 1970-01-01
      • 2014-03-20
      相关资源
      最近更新 更多