当前位置: 首页 > 知识库问答 >
问题:

Java-无需acquire即可释放信号量

冀望
2023-03-14

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

获得=随机数;释放=1个随机数

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

这是根据下面给出的文档支持的

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

问题是我的程序在1代表n后卡住了

我的程序如下:

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个答案

呼延化
2023-03-14

信号量的Javadoc。获取(int)表示:

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到达了行的前端,因此是下一个要分配的线程,因此获得了它的许可。

尉迟韬
2023-03-14

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

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

如果您继续运行它足够多的时间,您实际上会看到它成功完成。这是因为信号量是如何分发许可证的。您假设信号量一旦有足够的许可,就会尝试容纳一个acquire()调用。如果我们仔细查看信号量的文档。阿奎尔(int)我们会发现情况并非如此(我的重点):

如果没有足够的许可证可用,那么当前线程将出于线程调度目的而被禁用,并处于Hibernate状态,直到。。。其他一些线程为此信号量调用一个释放方法,当前线程是下一个被分配许可证的线程,可用许可证的数量满足此请求

换句话说,Semaphore保留一个挂起的获取请求队列,并且在每次调用.释放()时,只检查队列的头部。特别是如果您启用公平排队(将第二个构造函数参数设置为true),您将看到即使第一步也不会发生,因为第5步(通常)是队列中的第一步,甚至可以完成的新获取()调用也将排在其他挂起调用之后。

简而言之,这意味着您不能依赖于。acquire()以按照代码的假设尽快返回。

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

想象一下餐馆的等候名单。使用<代码>。aquire()就像把你的名字放在名单上,等待别人的召唤。这可能不是非常有效,但他们会在(合理的)相当长的时间内找到你。试想一下,如果每个人都对主持人大喊“你有桌子放n了吗?”尽可能多地使用-这是您的tryAquire()循环。它可能仍然有效(正如您的示例所示),但这肯定不是正确的方法

那么你应该怎么做呢?java中有许多可能有用的工具。util。并发的,哪一个最好在某种程度上取决于您正试图做什么。鉴于您可以有效地让每个线程启动下一个线程,我可以使用阻塞队列(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);
    }
  }
}

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

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的初学者,我正在试验信号量。我试图编写一个包含writer和reader的代码,我只尝试使用acquire()和release(): 1)如果一个作者在写作,那么同时没有其他作者可以写作,也没有读者可以阅读。 2)多个读者可以同时阅读,但是如果至少有一个活跃的读者,那么作者就不能写任何东西。 所以,总之,可以有 -一个读者和没有作者 -多个读者和没有作者 -一个作者和没有读者 我试着写

  • 问题内容: 我有一个大型的非Java EE,基于JSF的Web应用程序项目。我们的系统是分层的(在源代码中):有一个数据模型包,它是DAO包的基础。我们仅在DAO包中使用Hibernate的XML配置映射。我们确实不想将数据模型与注释混淆在一起,但是我们并没有特别地将其绑定到Hibernate(除非映射非常复杂)。 我强烈考虑向Java EE迈进,并将DAO对象构建为EJB。但是由于我们不愿放弃H

  • 我想根据用户名或频道id提取频道信息。此信息来自用于查看该频道的URL。 EX:https://www.youtube.com/user/csga5000,或 https://www.youtube.com/channel/some-channel-id 我有这个代码: 调用的函数是: 这是youtube类的构造函数: 使用“user/csga5000”调用函数也不起作用 打印的结果是: 我只想要

  • 我正在做一个项目,我们使用Tomcat8作为应用服务器,因此使用JSP和servlet。每个JSP都显示一种项目列表。应用程序的用户有可能模拟一个项目。这意味着什么并不重要。当用户单击“模拟”按钮时,将执行以下过程: 通过Ajax(javascript)将执行一个servlet,该servlet生成一个zip文件并将该文件存储在服务器的硬盘上 所以我的问题是:通知JSP模拟已经完成的最佳方式是什么

  • 我一直在使用macOS 10.15.3在Flutter中编程,目标是iOS设备。今天我也尝试安装Android Studio,以扩展。安装没有错误,但在中没有子目录,这是一个问题,因为应该在中找到。 我注意到这一点是因为运行< code>flutter doctor给了我: 我尝试重新安装Android Studio(没有区别)。在线发布的其他解决方案都谈到了Java版本,所以我将JRE从1.7升

  • 据我所知,线程可以释放信号量,而无需首先使用WaitOne获取锁。 所以,如果我们有线程A、B和C以及一个信号量,A和B调用WaitOne,得到一个锁,然后开始做他们的工作<线程C出现了,只需在信号量上调用Release。 这应该将信号量的计数增加1。这是否意味着信号量将终止A或B,或者只允许第三个线程获取锁并在其池中有3个线程,即使最大值为2?