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

时间:2011-10-12 16:18:10

标签: java multithreading deadlock semaphore

我有以下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,然后卡住。

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

2 个答案:

答案 0 :(得分:8)

基本问题是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();
}

<击>

(第二个想法,上面的内容也被打破,因为无法保证正确的线程会获得许可 - 它可以继续尝试并无限期地超时。)

答案 1 :(得分:0)

Semaphore 确实一次获得所有许可,否则它将不是真正的semaphore。但是:Java版本还有一个内部等待队列。并且该队列的行为 NOT 最适合当前的免费资源,但或多或​​少收集许可,直到可以允许队列中第一个请求为止。但是在线程进入该队列之前,如果可用的许可允许线程完全避免进入队列,则检查完成。

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

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

这意味着:如果

,每个线程都会被唤醒
  • 它位于队列的头部。
  • 已累积足够的许可证。