2016-05-02 77 views
6

我有给定随机数(1到n)的线程并被指示按排序顺序打印它们。我使用了信号量,以便获得许可证数量=随机数,并获得比获得的更多的许可证。Java - 没有获取信号量版本

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

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

这是支持按以下

给出的文件有()的释放许可线程必须通过调用获取已获得该许可证没有要求。

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

我的程序如下:

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

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

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

有人可以解释信号灯的许可证的获取机制,当多张线程与不同的许可请求等待?

+1

我很抱歉,我不能回答你的问题,但。不**在构造函数中执行'new Thread(this).start();'。由于您仍在构造函数中,因此对象不完整,您将部分初始化的对象提供给另一个方法,在这种情况下甚至会传送给另一个线程。这真的很糟糕,不要这样做。更好'扩展线程'而不是'实现Runnable',然后执行'new MyThread(i,sem).start();'或执行'new Thread(new MyThread(i,sem))。start();'。 – Vampire

+0

噢,我会验证这一点。感谢您指出。 – user1474053

+0

@BjörnKautler而不是仅仅说“*这真的很糟糕,不要这样做*。”试图解释*为什么*所以人们可以理解潜在的问题。 – dimo414

回答

4

这是一个聪明的策略,但你误解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)文件仔细看,我们会看到,是不是这种情况(重点煤矿):

如果足够的可用许可,那么当前线程用于线程调度目的,禁用并一直处于休眠状态.. 。其他一些线程调用release这个信号量的方法之一,当前线程接下来将被分配许可证,并且可用许可证的数量满足这个请求。

换句话说Semaphore保持未决获取请求的队列,并且在每次调用.release()只检查队列的头部。特别是如果您启用公平排队(将第二个构造函数参数设置为true),您甚至会发现第一步不会发生,因为步骤5(通常)是队列中的第一个,甚至可以实现的新呼叫将会排在其他未决呼叫之后。

简而言之,这意味着您不能依赖于.acquire()尽快返回,因为您的代码假定。

通过在循环中使用.tryAcquire()代替你避免作出任何阻塞调用(因此投入了大量的更多的负载您Semaphore),并尽快允许所需数量的可用一个tryAcquire()通话将顺利获得它们。这有效,但是很浪费。

在餐厅画一张等候名单。使用.aquire()就像在名单上列出你的名字并等待被调用。这可能不是很有效,但他们会在合理的时间内找到你。想象一下,如果每个人都只是在主持人喊道:“你有没有n的桌子?”尽可能经常 - 这是您的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 
+0

奇妙的做法。非常感谢,我现在明白了。我们也可以使用Atomic变量而不是BlockingQueue,或者可以使用Condition来避免轮询机制。 – user1474053

+0

@ user1474053很乐意提供帮助。根据你想要完成的事情,肯定有许多不同的合理解决方案,但是如果你想确保单独的线程按照规定的顺序运行,你应该使用某种阻塞机制;所有线程都需要定期轮询Atomic变量。 – dimo414

2

Semaphore.acquire(int)的Javadoc说:

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到达线路的前端,并因此被分配,因此获得许可。

+1

很好的解释。谢谢你的时间。我错过了解“当前的线程是旁边被分配的许可证”行。 – user1474053