让一个线程进入休眠状态,直到另一个线程解决了某个条件

Posted

技术标签:

【中文标题】让一个线程进入休眠状态,直到另一个线程解决了某个条件【英文标题】:Put one thread to sleep until a condition is resolved in another thread 【发布时间】:2010-09-24 02:03:01 【问题描述】:

这里有两段代码完成(我认为是)同样的事情。

我基本上是在尝试学习如何使用 Java 1.5 的并发来摆脱 Thread.sleep(long)。第一个示例使用 ReentrantLock,第二个示例使用 CountDownLatch。我想要做的主要是让一个线程进入睡眠状态,直到另一个线程解决了一个条件。

ReentrantLock 为我用来决定是否唤醒另一个线程的布尔值提供一个锁,然后我使用带有等待/信号的 Condition 来休眠另一个线程。据我所知,我需要使用锁的唯一原因是如果多个线程需要对布尔值的写访问。

CountDownLatch 似乎提供与 ReentrantLock 相同的功能,但没有(不必要的?)锁。然而,感觉就像我通过只需要一个倒计时来初始化它来劫持它的预期用途。我认为它应该在多个线程要处理同一个任务时使用,而不是在多个线程正在等待一个任务时使用。

所以,问题:

    我是否在 ReentrantLock 代码中为“正确的事情”使用了锁?如果我只在一个线程中写入布尔值,是否需要锁?只要我在唤醒任何其他线程之前重置布尔值就不会导致问题,可以吗?

    是否有一个类似于 CountDownLatch 的类可以用来避免更自然地适合此任务的锁(假设我应该在这种情况下避免它们)?

    还有其他方法可以改进我应该注意的代码吗?

示例一:

import java.util.concurrent.locks.*;

public class ReentrantLockExample extends Thread 

//boolean - Is the service down?
boolean serviceDown;

// I am using this lock to synchronize access to sDown
Lock serviceLock; 
// and this condition to sleep any threads waiting on the service.
Condition serviceCondition;

public static void main(String[] args) 
    Lock l = new ReentrantLock();
    Condition c = l.newCondition(); 
    ReentrantLockExample rle = new ReentrantLockExample(l, c);

    //Imagine this thread figures out the service is down
    l.lock();
    try 
        rle.serviceDown = true;
     finally 
        l.unlock();
    

    int waitTime = (int) (Math.random() * 5000);
    System.out.println("From main: wait time is " + waitTime);
    rle.start();
    try 
        //Symbolizes some random time that the service takes to come back up.
        Thread.sleep(waitTime);
     catch (InterruptedException e) 
        e.printStackTrace();
    

    //Imagine this thread figures out that the service is back up.
    l.lock();
    try 
        rle.serviceDown = false;
        c.signal();
     finally 
        l.unlock();
    



//Constructor
public ReentrantLockExample(Lock l, Condition c)   
    this.serviceLock = l;
    this.serviceCondition = c; 


/*
 * Should wait for this imaginary service to come back online.
 */
public void run() 
    System.out.println("Thread: start awaiting");
    serviceLock.lock();
    try 
        while (isServiceDown())
                   
            serviceCondition.await();
        
     catch (InterruptedException e) 
        // TODO Auto-generated catch block
        e.printStackTrace();
     finally 
        serviceLock.unlock();
    
    System.out.println("Thread: done awaiting");



private boolean isServiceDown()        
    return serviceDown;


示例二:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.*;

public class CountDownLatchExample extends Thread 

    //boolean - Is the service down?
    boolean serviceDown;

    // I am using this latch to wait on the service.
    CountDownLatch serviceLatch; 


    public static void main(String[] args) 
        CountDownLatch cdl = new CountDownLatch(1);     
        CountDownLatchExample cdle = new CountDownLatchExample(cdl);

        //Service goes down.
        cdle.serviceDown = true;        

        int waitTime = (int) (Math.random() * 5000);
        System.out.println("From main: wait time is " + waitTime);
        cdle.start();
        try 
            //Symbolizes some random time that the service takes to come back up.
            Thread.sleep(waitTime);
         catch (InterruptedException e) 
            e.printStackTrace();
        

        //Service comes back up.
        cdle.serviceDown = false;
        cdl.countDown();    
    

    //Constructor 
    public CountDownLatchExample(CountDownLatch cdl)   
        this.serviceLatch = cdl;         
    

    /*
     * Should wait for this imaginary service to come back online.
     */
    public void run() 
        System.out.println("Thread: start awaiting");
        try 
            while (isServiceDown())            
                serviceLatch.await();
            
         catch (InterruptedException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
        System.out.println("Thread: done awaiting");
    

    private boolean isServiceDown()        
        return serviceDown;
    

【问题讨论】:

【参考方案1】:

在我看来,锁定/条件变量方法更适合这项任务。这里有一个与您类似的示例:http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/locks/Condition.html

响应保护布尔值。你可以使用 volatile(http://www.ibm.com/developerworks/java/library/j-jtp06197.html)。然而,不使用锁的问题在于,根据您的服务关闭的时间长短,您将在 while(isServiceDown()) 上旋转。通过使用条件等待,您可以告诉操作系统让您的线程休眠,直到虚假唤醒(在 java 文档中讨论 Condition),或者直到在另一个线程中发出条件信号。

【讨论】:

如果你看第二个例子,CountDownLatch 可以在没有锁的情况下休眠: while (isServiceDown()) serviceLatch.await(); 我在原始帖子中明确指出了这一点。【参考方案2】:

这两种方法大致相同,只是CountDownLatch 只能释放一次。之后,所有await() 调用都会立即返回。因此,如果您使用的服务可能会下降或上升,CyclicBarrier 实际上可能更合适。

如果您的条件确实是一次性交易,那么FutureTask 会更合适。您可以调用 get() 等待服务可用,然后您可以在 get() 返回后立即使用该服务。

您提到 CountDownLatch 允许在不使用锁的情况下等待。但是,CountDownLatch 和ReentrantLock 都是使用AbstractQueuedSynchronizer 实现的。在底层,它们提供相同的同步和可见性语义。

【讨论】:

【参考方案3】:

FULL RE-ENTRANT LOCKED ASYNC TASK EXAMPLE:

带有代码流模式:

【讨论】:

以上是关于让一个线程进入休眠状态,直到另一个线程解决了某个条件的主要内容,如果未能解决你的问题,请参考以下文章

13.4 控制线程

13.4 控制线程

线程的等待状态

java多线程--线程休眠

线程同步之条件锁

Java线程休眠和线程让步