Java-无需获取即可发布信号量 [英] Java - Semaphore release without acquire

查看:56
本文介绍了Java-无需获取即可发布信号量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有被赋予随机数(从1到n)的线程,并被指示按排序顺序打印它们.我使用了信号灯,以便获得许可数量=随机数,并比获得的许可数量多释放一个许可.

I have threads which are given random number (1 to n) and are instructed to print them in sorted order. I used semaphore such that I acquire the number of permits = random number and release one permit more than what was acquired.

acquired =随机数;已发布= 1+随机数

acquired = random number; released = 1+random number

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

Initial permit count for semaphore is 1. So thread with random number 1 should get permit and then 2 and so on.

根据下面给出的文档

不要求释放许可证的线程必须已经通过调用acquire()获得了该许可证.

There is no requirement that a thread that releases a permit must have acquired that permit by calling acquire().

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

The problem is my program gets stuck after 1 for 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

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

如果我使用tryAcquire修改代码,则它运行得很好. 下面是新的运行实现

While If I modify my code with tryAcquire, it runs perfectly well. Below is new run implementation

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

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

Can someone please explain the semaphore's permit acquire mechanism when mulitple threads are waiting with different permit request??

推荐答案

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

It's a clever strategy, but you're misunderstanding how Sempahore hands out permits. If you run your code enough times you'll actually see it reach step two:

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()调用.如果我们仔细查看

If you keep on re-running it enough times you'd actually see it successfully finish. This happens because of how Semaphore hands out permits. You're assuming Semaphore will try to accommodate an acquire() call as soon as it has enough permits to do so. If we look carefully at the documentation for Semaphore.aquire(int) we'll see that is not the case (emphasis mine):

如果没有足够的许可,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到...某个其他线程为此信号量调用release方法之一,当前线程将紧随其后.分配的许可证,并且可用许可证的数量满足此请求.

If insufficient permits are available then the current thread becomes disabled for thread scheduling purposes and lies dormant until ... 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.

换句话说,Semaphore保留一个等待获取请求的队列,并且在每次调用.release()时,仅检查队列的头部.特别是,如果启用公平排队(将第二个构造函数参数设置为true),您甚至都不会看到第一个步骤,因为(通常)步骤5是队列中的第一个,甚至是新的acquire()调用可以满足的情况将排在其他待处理呼叫的后面.

In other words Semaphore keeps a queue of pending acquire request and, upon each call to .release(), only checks the head of the queue. In particular if you enable fair queuing (set the second constructor argument to true) you'll see even step one doesn't occur, because step 5 is (usually) the first in the queue and even new acquire() calls that could be fulfilled will be queued up behind the other pending calls.

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

In short this means you cannot rely on .acquire() to return as soon as possible, as your code assumes.

通过在循环中使用.tryAcquire()来避免进行任何阻塞调用(因此会给Semaphore带来更多负担),并且一旦获得必要数量的许可,tryAcquire()调用就会成功获得它们.这行得通,但很浪费.

By using .tryAcquire() in a loop instead you avoid making any blocking calls (and therefore put a lot more load on your Semaphore) and as soon as the necessary number of permits becomes available a tryAcquire() call will successfully obtain them. This works but is wasteful.

在餐厅为候补名单拍照.使用.aquire()就像将您的名字放在列表中并等待被叫.它可能效率不高,但是他们会在(合理的)相当长的时间内找到您.想象一下,如果所有人都对主持人大喊您有 n 的桌子吗?"尽可能多地-这就是您的tryAquire()循环.它可能仍然可以解决问题(如您的示例中所示),但是肯定不是正确的解决方法.

Picture a wait-list at a restaurant. Using .aquire() is like putting your name on the list and waiting to be called. It may not be perfectly efficient, but they'll get to you in a (reasonably) fair amount of time. Imagine instead if everyone just shouted at the host "Do you have a table for n yet?" as often as they could - that's your tryAquire() loop. It may still work out (as it does in your example) but it's certainly not the right way to go about it.

那你应该怎么做呢? java.util.concurrent ,这最好在某种程度上取决于您要尝试执行的操作.看到您正在有效地使每个线程开始下一个线程,我可能会使用BlockingQueue作为同步辅助,每次都将下一步推入队列.然后,每个线程都会轮询队列,如果不是激活的线程,则替换该值,然后再次等待.

So what should you do instead? There's a number of possibly useful tools in java.util.concurrent, and which is best somewhat depends on what exactly you're trying to do. Seeing as you're effectively having each thread start the next one I might use a BlockingQueue as the synchronization aid, pushing the next step into the queue each time. Each thread would then poll the queue, and if it's not the activated thread's turn replace the value and wait again.

这是一个例子:

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);
    }
  }
}

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

This prints the following and terminates successfully:

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

这篇关于Java-无需获取即可发布信号量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆