We consider each pair of threads Tw, Ts
such as Tw
is waiting on Ts
to commence its work. In your setup, there are 2 such pairs:
T1, T3
T2, T1
For each pair, we will create one CountDownLatch
, and provide it to each thread of the pair. Then Tw
will call await
on the latch before starting its work, and Ts
will call countDown
at the end of its own work.
Since T1
belongs to both pairs, it will receive both latches. However, in the first case, T1
is a waiting thread, and in the second, T1
is a signaling thread, therefore its code will have to be amended accordingly.
Of course you will have to remove the join
calls and related infrastructure.
Since your question title asks about latch implementation, let's just briefly say that the same semantics can be produced using a Semaphore
initialized at 0
, and where countDown
would actually be a release
of the semaphore, while await
would be an acquire
of that semaphore.
public class Test {
private CountdownLatch latch;
private Runnable runnable;
class Tw implements Runnable {
Tw(CountdownLatch l, Runnable r) {
latch = l;
runnable = r;
}
@override
public void run(){
latch.await();
runnable.run();
}
}
class Ts implements Runnable {
CountdownLatch latch;
Runnable runnable;
Ts(CountdownLatch l, Runnable r){
latch = l;
runnable = r;
}
@override
public void run(){
runnable.run();
latch.countDown();
}
}
static class Printer implements Runnable {
private final int from;
private final int to;
Printer(int from, int to) {
this.from = from;
this.to = to;
}
@Override
public void run() {
for (int i = from; i <= to; i++) {
System.out.println(i);
}
}
public static void main(String[] args) throws InterruptedException {
CountdownLatch l31 = new CountdownLatch(1), l12 = new CountdownLatch(1);
Thread T3 = new Thread(new Ts(l31, new Printer(10, 15, null)));
Thread T1 = new Thread(new Tw(l31, new Ts(l12, new Printer(1, 5, T3))));
Thread T2 = new Thread(new Tw(l12, new Printer(6, 10, T1)));
T1.start();
T2.start();
T3.start();
}
}
The proposed sample implementation uses auxiliary runnables to take care of the latching process, thus allowing us to compose each task using these runnables, instead of deriving the Printer
class for each specific case (we save at least one class).
The Semaphore
based similar implementation is left as an exercise for the reader.