我有随机数(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);
}
当多个线程正在等待不同的许可请求时,有人可以解释一下信号量的许可获取机制吗?
答案 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到达线的前面,因此接下来被分配,因此获得许可。