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

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)

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

dim*_*414 5

这是一个聪明的策略,但是您误会了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)

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

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
Run Code Online (Sandbox Code Playgroud)