【问题标题】:Deadlock in Java code with Semaphore and acquire(int)信号量和获取(int)在Java代码中的死锁
【发布时间】:2011-12-06 06:59:03
【问题描述】:

我有以下 Java 代码:

import java.util.concurrent.*;

class Foo{
    static Semaphore s = new Semaphore(1);

    public void fun(final char c, final int r){
        new Thread(new Runnable(){
            public void run(){
                try{ 
                    s.acquire(r);
                    System.out.println(c+"_"+r);
                    s.release(r+1);
                } catch(Exception e){ e.printStackTrace(); }
            }
        }).start();
    }
}

class ths{
    public static void main(String[]args) throws Exception{
        Foo f = new Foo();
        f.fun('B',2);
        f.fun('F',6);
        f.fun('A',1);
        f.fun('C',3);
        f.fun('D',4);
        f.fun('E',5);
    }
}

理想情况下,这应该按顺序打印 A_1 到 F_6 并退出,但由于某种原因,这不会发生。它通常打印 A_1 和 B_2 然后卡住了。

我找不到我的代码有任何明显错误的地方。有什么建议吗?

【问题讨论】:

    标签: java multithreading deadlock semaphore


    【解决方案1】:

    基本的问题是acquire(int permits) 不保证所有的许可证都会被一次抓取。它可以获取更少的许可,然后在等待其余许可时阻塞。

    让我们考虑一下您的代码。例如,当三个许可证可用时,无法保证它们将被授予线程C。事实上,它们可以被赋予线程D 以部分满足其acquire(4) 请求,从而导致死锁。

    如果您像这样更改代码,这将为我解决问题:

    public void fun(final char c, final int r){
        new Thread(new Runnable(){
            public void run(){
                try{ 
                    while (!s.tryAcquire(r, 1, TimeUnit.MILLISECONDS)) {};
                    System.out.println(c+"_"+r);
                    s.release(r+1);
                } catch(Exception e){ e.printStackTrace(); }
            }
        }).start();
    }
    

    (再想一想,上面的内容也被破坏了,因为不能保证正确的线程会获得许可——它​​可能会无限期地尝试和超时。)

    【讨论】:

    • 这确实解决了它,但我想知道为什么它是必要的。如果在打印 B_2 后卡住了,在主线程中,一段时间后,我打印可用许可的数量,它会打印 3。那么为什么不允许线程 C 继续?
    • @Vlad:我的猜测是3 并没有反映出D 可能已经为它的acquire(4) 调用“保留”了一些许可。
    • 我认为你可能是对的。我没有足够仔细地阅读要发布的文档。 link。我曾假设实现会循环遍历线程并尝试找到一个它可以真正唤醒的。
    【解决方案2】:

    Semaphore 确实一次获得所有许可,否则它就不是真正的semaphore。但是:Java 版本也有一个内部等待队列。该队列的行为是 为当前可用资源提供最佳匹配,但或多或​​少收集许可,直到队列中的第一个请求被允许。但在线程进入该队列之前,会检查是否可用的许可允许线程完全避免进入队列。

    我已修改您的代码以显示该队列行为:

    import java.util.concurrent.*;
    public class SemaphoreTest{
        static Semaphore s = new Semaphore(0);
    
        public void fun(final char c, final int r) throws Exception {
            new Thread(new Runnable(){
                public void run(){
                    try{ 
                        System.out.println("acquire "+r);
                        s.acquire(r);
                        System.out.println(c+"_"+r);
                    } catch(Exception e){ e.printStackTrace(); }
                }
            }).start();
            Thread.sleep(500);
        }
    
        public static void main(String[]args) throws Exception{
            SemaphoreTest f = new SemaphoreTest();
    
            f.fun('B',2);
            f.fun('F',6);
            f.fun('A',1);
            f.fun('C',3);
            f.fun('D',4);
            f.fun('E',5);
    
            while(s.hasQueuedThreads()){
                Thread.sleep(1000);
                System.out.println("release "+1+", available "+(s.availablePermits()+1));
                s.release(1);
            }
        }
    }
    

    基本上做了以下改动:

    • 从 0 个许可开始 - 让任何人先进入队列。
    • 通过在Thread.start 之后给每个线程 500 毫秒时间来“定义”排队顺序。
    • 每个线程都会调用acquire,但不会调用release
    • 主线程将一个接一个地缓慢地提供信号量。

    这将给出确定性的输出:

    acquire 2
    acquire 6
    acquire 1
    acquire 3
    acquire 4
    acquire 5
    release 1, available 1
    release 1, available 2
    B_2
    release 1, available 1
    release 1, available 2
    release 1, available 3
    release 1, available 4
    release 1, available 5
    release 1, available 6
    F_6
    release 1, available 1
    A_1
    release 1, available 1
    release 1, available 2
    release 1, available 3
    C_3
    release 1, available 1
    release 1, available 2
    release 1, available 3
    release 1, available 4
    D_4
    release 1, available 1
    release 1, available 2
    release 1, available 3
    release 1, available 4
    release 1, available 5
    E_5
    release 1, available 1
    

    这意味着:每个线程都被唤醒,如果

    • 它排在队列的最前面。
    • 已累积足够的许可。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-02
      • 2021-03-20
      • 1970-01-01
      • 2021-05-09
      • 1970-01-01
      相关资源
      最近更新 更多