use*_*053 7 java concurrency multithreading 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);
}
}
Run Code Online (Sandbox Code Playgroud)
输出是:
获取锁定 - 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);
}
Run Code Online (Sandbox Code Playgroud)
当多个线程正在等待不同的许可请求时,有人可以解释一下信号量的许可获取机制吗?
这是一个聪明的策略,但是您误会了Sempahore
派发许可的方式。如果您运行了足够多次的代码,您实际上会看到它到达了第二步:
Acquiring lock -- 5
Acquiring lock -- 1
1
Releasing lock -- 1
Acquiring lock -- 3
Acquiring lock -- 2
2
Acquiring lock -- 4
Releasing lock -- 2
Run Code Online (Sandbox Code Playgroud)
如果您继续重新运行它足够多次,则实际上您会看到它成功完成。发生这种情况的原因是Semaphore
派发许可的方式。您假设只要有足够的许可,Semaphore
便会尝试容纳该acquire()
呼叫。如果我们仔细查看文档,Semaphore.aquire(int)
将会发现情况并非如此(强调我的意思):
如果没有足够的许可,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到...其他某个线程
release
为此信号量调用一种方法,接下来将为当前线程分配许可,并且可用许可的数量将满足这个请求。
换句话说,Semaphore
将等待获取请求的队列保留,并且在每次调用时.release()
,仅检查队列的头部。特别是,如果启用公平排队(将第二个构造方法参数设置为true
),您甚至会看到第一个步骤没有发生,因为第5步(通常)是队列中的第一个,甚至acquire()
可以满足的新调用也将排队在其他待处理呼叫之后。
简而言之,这意味着您不能.acquire()
像代码所假设的那样尽快返回。
通过.tryAcquire()
在循环中使用,可以避免进行任何阻塞调用(并因此给您增加了更多的负担Semaphore
),并且一旦获得必要数量的许可,tryAcquire()
呼叫就会成功获取它们。这可行,但很浪费。
在餐厅想象一个等候名单。使用.aquire()
就像将您的名字放在列表中并等待被叫。它可能效率不高,但是他们会在(合理的)相当长的时间内找到您。试想一下,如果每个人都只是对主持人大喊“您有桌子n
吗?” 尽可能多地-这就是您的tryAquire()
循环。它可能仍然可以解决问题(如您的示例所示),但这当然不是正确的解决方法。
那么,您应该怎么做呢?中有许多可能有用的工具java.util.concurrent
,最好在某种程度上取决于您要尝试执行的操作。看到您正在有效地使每个线程开始下一个线程,我可能会使用a BlockingQueue
作为同步辅助,每次都将下一步推入队列。然后,每个线程将轮询队列,如果不是激活的线程,则替换该值,然后再次等待。
这是一个例子:
public class MultiThreading {
public static void main(String[] args) throws Exception{
// Use fair queuing to prevent an out-of-order task
// from jumping to the head of the line again
// try setting this to false - you'll see far more re-queuing calls
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1, true);
for (int i = 5; i >= 1; i--) {
Thread.sleep(100); // not necessary, just helps demonstrate the queuing behavior
new MyThread(i, queue).start();
}
queue.add(1); // work starts now
}
static class MyThread extends Thread {
int var;
BlockingQueue<Integer> queue;
public MyThread(int var, BlockingQueue<Integer> queue) {
this.var = var;
this.queue = queue;
}
@Override
public void run() {
System.out.println("Task " + var + " is now pending...");
try {
while (true) {
int task = queue.take();
if (task != var) {
System.out.println(
"Task " + var + " got task " + task + " instead - re-queuing");
queue.add(task);
} else {
break;
}
}
} catch (InterruptedException e) {
// If a thread is interrupted, re-mark the thread interrupted and terminate
Thread.currentThread().interrupt();
return;
}
System.out.println("Finished task " + var);
System.out.println("Registering task " + (var + 1) + " to run next");
queue.add(var + 1);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这将打印以下内容并成功终止:
Run Code Online (Sandbox Code Playgroud)Task 5 is now pending... Task 4 is now pending... Task 3 is now pending... Task 2 is now pending... Task 1 is now pending... Task 5 got task 1 instead - re-queuing Task 4 got task 1 instead - re-queuing Task 3 got task 1 instead - re-queuing Task 2 got task 1 instead - re-queuing Finished task 1 Registering task 2 to run next Task 5 got task 2 instead - re-queuing Task 4 got task 2 instead - re-queuing Task 3 got task 2 instead - re-queuing Finished task 2 Registering task 3 to run next Task 5 got task 3 instead - re-queuing Task 4 got task 3 instead - re-queuing Finished task 3 Registering task 4 to run next Task 5 got task 4 instead - re-queuing Finished task 4 Registering task 5 to run next Finished task 5 Registering task 6 to run next