Java 代码中的死锁,使用 Semaphore 和 acquire(int)

2022-09-04 22:36:11

我有以下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);
    }
}

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

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


答案 1

基本问题是,获取(int许可证)并不能保证所有许可证都会被立即获得。它可以获得更少的许可证,然后在等待其余许可证时阻止。

让我们考虑一下您的代码。例如,当三个许可证可用时,没有什么可以保证它们将被授予线程 。事实上,它们可以被赋予线程以部分满足其请求,从而导致死锁。CDacquire(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();
}

(仔细想想,上述内容也被打破了,因为无法保证正确的线程会获得许可 - 它可能会无限期地尝试和超时。


答案 2

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 个许可证开始 - 让任何人先进入队列。
  • “定义”排队的顺序,方法是在 之后给每个线程 500 毫秒的时间。Thread.start
  • 每个线程将调用 ,但不会调用 。acquirerelease
  • 主线程将用一个接一个的许可证缓慢地馈送信号量。

这将确定性地给出此输出:

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

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

  • 它位于队列的首位。
  • 已经积累了足够的许可证。