jjzjj

Java - 无需获取的信号量释放

coder 2024-03-30 原文

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

acquired = random number; released = 1+random number

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

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

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

问题是我的程序在 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);
    }
}

输出是:

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

虽然如果我用 tryAcquire 修改我的代码,它运行得很好。 下面是新的运行实现

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

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

最佳答案

这是一个聪明的策略,但您误解了 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) 的文档我们会看到情况并非如此(强调我的):

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),您会看到甚至不会发生第 1 步,因为第 5 步(通常)是队列中的第一个,甚至是新的acquire() 可以完成的调用将排在其他未决调用之后。

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

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

想象一下餐厅的候补名单。使用 .aquire() 就像将您的名字放在列表中并等待被调用。它可能不是非常有效,但他们会在(合理的)相当长的时间内找到你。想象一下,如果每个人都对主持人大喊“你有 n 的 table 了吗?”尽可能频繁——这就是你的 tryAquire() 循环。它可能仍然有效(就像在您的示例中所做的那样),但这肯定不是正确的方法。


那么你应该怎么做呢? java.util.concurrent 中有许多可能有用的工具,哪个最好取决于你到底想做什么。鉴于您正在有效地让每个线程启动下一个线程,我可能会使用 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 - 无需获取的信号量释放,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36992758/

有关Java - 无需获取的信号量释放的更多相关文章

  1. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  2. ruby - 简单获取法拉第超时 - 2

    有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

  3. ruby - 从 Ruby 中的主机名获取 IP 地址 - 2

    我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge

  4. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

  5. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

    我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

  6. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  7. jquery - 我的 jquery AJAX POST 请求无需发送 Authenticity Token (Rails) - 2

    rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送

  8. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  9. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  10. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

随机推荐