Java - 没有获得的信号量发布

时间:2016-05-02 22:40:24

标签: java multithreading concurrency semaphore

我有随机数(1到n)给出的线程,并指示按排序顺序打印它们。我使用信号量,以便获得许可数量=随机数,并释放一个许可证,而不是获得许可证。

  

获得=随机数;已发布= 1 +随机数

信号量的初始许可数为1.因此,随机数1的线程应该获得许可,然后是2,依此类推。

根据以下文档支持

  

不要求发布许可证的线程必须通过调用acquire()来获取该许可证。

问题是我的程序在1> n> 2后卡住了。

我的计划如下:

import java.util.concurrent.Semaphore;

public class MultiThreading {
    public static void main(String[] args) {
        Semaphore sem = new Semaphore(1,false);
        for(int i=5;i>=1;i--)
            new MyThread(i, sem);
    }
}
class MyThread implements Runnable {
    int var;Semaphore sem;
    public MyThread(int a, Semaphore s) {
        var =a;sem=s;
        new Thread(this).start();
    }
    @Override
    public void run() {
        System.out.println("Acquiring lock -- "+var);
        try {
            sem.acquire(var);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(var);

        System.out.println("Releasing lock -- "+var);
        sem.release(var+1);
    }
}

输出是:

  

获取锁定 - 4
  获取锁定 - 5
  获取锁定 - 3
  获取锁定 - 2
  获取锁定 - 1
  1
  释放锁定 - 1

虽然如果我用tryAcquire修改我的代码,它运行得非常好。 以下是新运行实现

@Override
public void run() {
    boolean acquired = false;
    while(!acquired) {
        acquired = sem.tryAcquire(var);
    }
    System.out.println(var);
    sem.release(var+1);
}

当多个线程正在等待不同的许可请求时,有人可以解释一下信号量的许可获取机制吗?

2 个答案:

答案 0 :(得分:4)

这是一个聪明的策略,但你误解了Sempahore如何发放许可证。如果您运行的代码足够多次,您实际上会看到它到达第二步:

Acquiring lock -- 5
Acquiring lock -- 1
1
Releasing lock -- 1
Acquiring lock -- 3
Acquiring lock -- 2
2
Acquiring lock -- 4
Releasing lock -- 2

如果你继续重新运行它足够多次,你实际上已经看到它成功完成了。这是因为Semaphore如何发放许可证。您假设Semaphore只要有足够的许可证就会尝试接听acquire()电话。如果我们仔细查看Semaphore.aquire(int)的文档,我们会发现情况并非如此(强调我的):

  

如果没有足够的许可证,那么当前线程将被禁用以进行线程调度,并且处于休眠状态,直到......其他一些线程调用此信号量的release方法之一,当前线程是下一个分配许可证,可用许可证数量满足此要求。

换句话说,Semaphore保留待处理获取请求的队列,并且在每次调用.release()时,仅检查队列的头部。特别是如果启用公平排队(将第二个构造函数参数设置为true),您甚至会看到第一步没有发生,因为步骤5(通常)是队列中的第一个,甚至是新的{{1可以实现的调用将排在其他待处理调用之后。

简而言之,这意味着您不能依赖acquire()尽快返回,正如您的代码所假设的那样。

在循环中使用.acquire()而不是进行任何阻止调用(因此会对.tryAcquire()施加更多负担)并且只要有必要数量的许可证就可以{{{ 1}}调用将成功获取它们。这有效,但很浪费。

在餐厅画一张等候名单。使用Semaphore就像把你的名字放在列表上等待被叫。它可能效率不高,但它们会在(合理)相当长的时间内找到你。想象一下,如果每个人都只是对主持人大喊“你有 tryAcquire() 的表吗?”尽可能经常 - 这是你的.aquire()循环。它可能仍然有用(正如你的例子中所做的那样),但它肯定不是正确的方法。

那你该怎么做呢? java.util.concurrent中有许多可能有用的工具,最好的取决于你究竟要做什么。看到你有效地让每个线程开始下一个线程我可能会使用n作为同步辅助,每次都将下一步推入队列。然后每个线程轮询队列,如果不是激活的线程,则替换该值并再次等待。

以下是一个例子:

tryAquire()

这将打印以下内容并成功终止:

BlockingQueue

答案 1 :(得分:2)

Semaphore.acquire(int)的Javadoc说:

If insufficient permits are available then the current thread becomes 
disabled for thread scheduling purposes and lies dormant until one of 
two things happens:

Some other thread invokes one of the release methods for this semaphore, 
the current thread is next to be assigned permits and the number of 
available permits satisfies this request [or the thread is interrupted].

“下一个要分配”的线程可能是您示例中的线程4。它等待有4个许可证可用。但是,在调用acquire()时获得许可的线程1只释放2个许可,这不足以解除对线程4的阻塞。同时,线程2是唯一有足够许可的线程,不是下一个被分配,所以没有获得许可。

您修改后的代码运行正常,因为线程在尝试获取信号量时不会阻塞;他们只是再试一次,走到后面。最终,线程2到达线的前面,因此接下来被分配,因此获得许可。