2011-10-12 44 views
12

,我有以下的Java代码:死锁与信号灯的Java代码,并获得(INT)

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,然后卡住。

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

回答

7

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

(退一步来说,上面还坏,因为谁也不能保证正确的线程将永远得到许可证 - 它可以继续尝试和无限期地超时。)

+0

这确实解决了它,但我想知道为什么它是必要的。如果在打印B_2之后卡住了,并且在主线程中,经过一段时间,我打印出可用许可证的数量,它会打印3.那么为什么不允许线程C继续? – Vlad

+0

@Vlad:我的猜测是'3'没有反映出'D'可能已经为其获取(4)'呼叫保留了一些许可。 – NPE

+0

我想你可能是对的。我没有仔细阅读发布的文档。 [链接](http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Semaphore.html#release(INT))。我曾假设这个实现会围绕这些线程循环,并试图找到一个可以真正唤醒的实现。 – Vlad

0

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之后给每个线程500ms的时间“定义”排队顺序。
  • 每个线程都会调用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 

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

  • 它在队列的头。
  • 已经积累了足够的许可证。