nimo23
nimo23

Reputation: 5700

ReentrantLock as replacement for CountdownLatch

I have myCountDownLatch (which works as expected):

public static void myCountDownLatch() {

    CountDownLatch countDownLatch = new CountDownLatch(1);
    Thread t = new Thread(() ->
    {
        try {
            log.info("CountDownLatch: in thread..");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        countDownLatch.countDown();
    });
    t.start();
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("CountDownLatch: out thread..");
}

I am trying to understand the difference of CountdownLatch and ReentrantLock and tried to rewrite myCountDownLatch by using ReentrantLock instead of CountdownLatch:

public static void myRentrantLock() {

    ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    Thread t = new Thread(() ->
    {
        try {
            log.info("ReentrantLock: in thread..");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

    lock.lock();
    t.start();
    lock.unlock();

    log.info("ReentrantLock: out thread..");
}

I only want to stop the main thread as long as Thread t is not finished by using ReentrantLock instead of CountDownLatch.

However, myRentrantLock does not behave equal to my myCountDownLatch. Why?

Upvotes: 1

Views: 1154

Answers (1)

Holger
Holger

Reputation: 298233

You can not replace a countdown latch with a ReentrantLock, which is a tool for mutual exclusion and notification, but you could use a ReentrantLock to implement a similar functionality.

It may look like

public class MyLatch {
    final ReentrantLock lock = new ReentrantLock();
    final Condition zeroReached = lock.newCondition();
    int remaining;

    MyLatch(int count) {
        if(count < 0) throw new IllegalArgumentException();
        remaining = count;
    }
    public void await() throws InterruptedException {
        lock.lock();
        try {
            while(remaining != 0) zeroReached.await();
        }
        finally {
            lock.unlock();
        }
    }
    public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
        lock.lock();
        try {
            if(remaining == 0) return true;
            long deadLine = System.nanoTime() + unit.toNanos(timeout);
            while(remaining != 0) {
                final long remainingTime = deadLine - System.nanoTime();
                if(remainingTime <= 0) return false;
                zeroReached.await(remainingTime, TimeUnit.NANOSECONDS);
            }
            return true;
        }
        finally {
            lock.unlock();
        }
    }
    public void countDown() {
        lock.lock();
        try {
            if(remaining > 0 && --remaining == 0) zeroReached.signalAll();
        }
        finally {
            lock.unlock();
        }
    }
    public long getCount() {
        lock.lock();
        try {
          return remaining;
        }
        finally {
            lock.unlock();
        }
    }
}

The ReentrantLock guards the internal state, which is the remaining field. The associated Condition zeroReached is used to allow threads waiting for the remaining field to become zero.

This can be used the same way as the builtin CountDownLatch:

public class MyLatchTest {
    public static void main(String[] args) {
        int num = 10;
        MyLatch countDownLatch = new MyLatch(num);
        for(int i = 0; i < num; i++) {
            Thread t = new Thread(() ->
            {
                try {
                    System.out.println("CountDownLatch: in thread..");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("CountDownLatch: one thread finished..");
                countDownLatch.countDown();
            });
            t.start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("CountDownLatch: out thread..");
    }
}

Note that you don’t need an explicit Lock here, Java’s intrinsic locking feature would work as well:

public class MyLatch {
    int remaining;

    MyLatch(int count) {
        if(count < 0) throw new IllegalArgumentException();
        remaining = count;
    }
    public synchronized void await() throws InterruptedException {
        while(remaining != 0) wait();
    }
    public synchronized boolean await(long timeout, TimeUnit unit) throws InterruptedException {
        if(remaining == 0) return true;
        long deadLine = System.nanoTime() + unit.toNanos(timeout);
        while(remaining != 0) {
            long remainingTime = deadLine - System.nanoTime();
            if(remainingTime <= 0) return false;
            wait(remainingTime / 1_000_000, (int)(remainingTime % 1_000_000));
        }
        return true;
    }
    public synchronized void countDown() {
        if(remaining > 0 && --remaining == 0) notifyAll();
    }
    public synchronized long getCount() {
        return remaining;
    }
}

But in either case, the builtin CountDownLatch is more efficient…

Upvotes: 1

Related Questions