views:

1294

answers:

2

Here are two chunks of code that accomplish (what I think is) the same thing.

I basically am trying to learn how to use Java 1.5's concurrency to get away from Thread.sleep(long). The first example uses ReentrantLock, and the second example uses CountDownLatch. The jist of what I am trying to do is put one thread to sleep until a condition is resolved in another thread.

The ReentrantLock provides a lock on the boolean I'm using to decide whether to wake the other thread or not, and then I use the Condition with await/signal to sleep the other thread. As far as I can tell, the only reason I would need to use locks is if more than one thread required write access to the boolean.

The CountDownLatch seems to provide the same functionality as the ReentrantLock but without the (unnecessary?) locks. However, it feels like I am kind of hijacking its intended use by initializing it with only one countdown necessary. I think it's supposed to be used when multiple threads are going to be working on the same task, not when multiple threads are waiting on one task.

So, questions:

  1. Am I using locks for "the right thing" in the ReentrantLock code? If I am only writing to the boolean in one thread, are the locks necessary? As long as I reset the boolean before waking up any other threads I can't cause a problem, can I?

  2. Is there a class similar to CountDownLatch I can use to avoid locks (assuming I should be avoiding them in this instance) that is more naturally suited to this task?

  3. Are there any other ways to improve this code I should be aware of?

EXAMPLE ONE:

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

}

EXAMPLE TWO:

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

}

A: 

The lock/condition variable approach is better for this task in my opinion. There is a similar example to yours here: http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/locks/Condition.html

In response to protecting a boolean. You could use volatile(http://www.ibm.com/developerworks/java/library/j-jtp06197.html). However the problem with not using Locks is that depending on how long your service is down for you will be spinning on while(isServiceDown()). By using a condition wait you tell the OS to sleep your thread until a spurious wake up (talked about in the java docs for Condition), or until the condition is signaled in the other thread.

grepsedawk
If you look here in the second example, the CountDownLatch can sleep without locks: while (isServiceDown()) { serviceLatch.await(); }Which I was kind of explicit about in my original post.
Nathan Spears
+2  A: 

Either approach is roughly equivalent, except that a CountDownLatch can only be released once. After that all await() calls return instantly. So a CyclicBarrier may actually be more appropriate if you are working with a service that may go down and up.

If your condition really is a one-shot deal, then a FutureTask would be more appropriate. You could call get() which would wait for the service to become available, and then you could use the service as soon as get() returns.

You mention that CountDownLatch allows waiting without using Locks. However, both CountDownLatch and ReentrantLock are implemented using AbstractQueuedSynchronizer. Under the hood, they provide identical synchronization and visibility semantics.

Craig P. Motlin