Let's consider the following piece of code:
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cb = new CyclicBarrier(3, () -> {
logger.info("Barrier action starting");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("Barrier action finishing");
});
for (int i = 0; i < 6; i++) {
int counter = i;
Thread.sleep(100);
new Thread(() -> {
try {
logger.info("Try to acquire barrier for {}", counter);
cb.await();
logger.info("barrier acquired for {}", counter);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
I've created barrier with size = 3 and barrirer action which takes 5 seconds.
I see following output:
2019-10-27 15:23:09.393 INFO --- [ Thread-0] my.playground.RemoteServiceFacade : Try to acquire barrier for 0
2019-10-27 15:23:09.492 INFO --- [ Thread-1] my.playground.RemoteServiceFacade : Try to acquire barrier for 1
2019-10-27 15:23:09.593 INFO --- [ Thread-2] my.playground.RemoteServiceFacade : Try to acquire barrier for 2
2019-10-27 15:23:09.594 INFO --- [ Thread-2] my.playground.RemoteServiceFacade : Barrier action starting
2019-10-27 15:23:09.693 INFO --- [ Thread-3] my.playground.RemoteServiceFacade : Try to acquire barrier for 3
2019-10-27 15:23:09.794 INFO --- [ Thread-4] my.playground.RemoteServiceFacade : Try to acquire barrier for 4
2019-10-27 15:23:09.897 INFO --- [ Thread-5] my.playground.RemoteServiceFacade : Try to acquire barrier for 5
2019-10-27 15:23:14.594 INFO --- [ Thread-2] my.playground.RemoteServiceFacade : Barrier action finishing
2019-10-27 15:23:14.595 INFO --- [ Thread-2] my.playground.RemoteServiceFacade : barrier acquired for 2
2019-10-27 15:23:14.595 INFO --- [ Thread-5] my.playground.RemoteServiceFacade : Barrier action starting
2019-10-27 15:23:19.596 INFO --- [ Thread-5] my.playground.RemoteServiceFacade : Barrier action finishing
2019-10-27 15:23:19.597 INFO --- [ Thread-0] my.playground.RemoteServiceFacade : barrier acquired for 0
2019-10-27 15:23:19.597 INFO --- [ Thread-4] my.playground.RemoteServiceFacade : barrier acquired for 4
2019-10-27 15:23:19.597 INFO --- [ Thread-3] my.playground.RemoteServiceFacade : barrier acquired for 3
2019-10-27 15:23:19.597 INFO --- [ Thread-1] my.playground.RemoteServiceFacade : barrier acquired for 1
2019-10-27 15:23:19.597 INFO --- [ Thread-5] my.playground.RemoteServiceFacade : barrier acquired for 5
So we can see that:
- First barrier action lasts 15:23:09 - 15:23:14
- Second barrier action lasts 15:23:14 - 15:23:19
But only one thread was able to log after first barrier action termination:
2019-10-27 15:23:14.595 INFO --- [ Thread-2] my.playground.RemoteServiceFacade : barrier acquired for 2
I expected 3 threads should be able to acquire approximately at 15:23:14 because CyclicBarrier size is 3.
Could you explain this behaviour?
P.S.
I tried to run this code a lot of time and always the similar result.
P.S.2.
I tried to change timings a bit:
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cb = new CyclicBarrier(3, () -> {
logger.info("Barrier action starting");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("Barrier action finishing");
});
for (int i = 0; i < 6; i++) {
int counter = i;
Thread.sleep(1000);
new Thread(() -> {
try {
logger.info("Try to acquire barrier for {}", counter);
cb.await();
logger.info("barrier acquired for {}", counter);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
And I see expected results:
2019-10-27 23:22:14.497 INFO --- [ Thread-0] my.playground.RemoteServiceFacade : Try to acquire barrier for 0
2019-10-27 23:22:15.495 INFO --- [ Thread-1] my.playground.RemoteServiceFacade : Try to acquire barrier for 1
2019-10-27 23:22:16.495 INFO --- [ Thread-2] my.playground.RemoteServiceFacade : Try to acquire barrier for 2
2019-10-27 23:22:16.496 INFO --- [ Thread-2] my.playground.RemoteServiceFacade : Barrier action starting
2019-10-27 23:22:16.998 INFO --- [ Thread-2] my.playground.RemoteServiceFacade : Barrier action finishing
2019-10-27 23:22:16.998 INFO --- [ Thread-0] my.playground.RemoteServiceFacade : barrier acquired for 0
2019-10-27 23:22:16.998 INFO --- [ Thread-2] my.playground.RemoteServiceFacade : barrier acquired for 2
2019-10-27 23:22:16.998 INFO --- [ Thread-1] my.playground.RemoteServiceFacade : barrier acquired for 1
2019-10-27 23:22:17.495 INFO --- [ Thread-3] my.playground.RemoteServiceFacade : Try to acquire barrier for 3
2019-10-27 23:22:18.495 INFO --- [ Thread-4] my.playground.RemoteServiceFacade : Try to acquire barrier for 4
2019-10-27 23:22:19.496 INFO --- [ Thread-5] my.playground.RemoteServiceFacade : Try to acquire barrier for 5
2019-10-27 23:22:19.499 INFO --- [ Thread-5] my.playground.RemoteServiceFacade : Barrier action starting
2019-10-27 23:22:20.002 INFO --- [ Thread-5] my.playground.RemoteServiceFacade : Barrier action finishing
2019-10-27 23:22:20.003 INFO --- [ Thread-5] my.playground.RemoteServiceFacade : barrier acquired for 5
2019-10-27 23:22:20.003 INFO --- [ Thread-3] my.playground.RemoteServiceFacade : barrier acquired for 3
2019-10-27 23:22:20.003 INFO --- [ Thread-4] my.playground.RemoteServiceFacade : barrier acquired for 4
A fascinating question, it's not super trivial but I'll try to explain it as succinctly as I can.
Although multithreading doesn't guarantee any type of execution order, for this answer let's imagine there are two sequences that happen first:
barrier.await()at the same time.In this case you'd see an output like
The current state of your 6 threads are as follows:
Threads
0and1awaiton a sharedConditionsince three threads have not reached the barrier "yet"Thread
2is still runnable as the "tripping" thread for the barrierThreads
3,4and5will wait on alock.lockinvocation of the barrier (the sameLockinstance in which theConditionwas created.When the barrier sees thread
2it previously recorded0and1as reaching the barrier so it knows that this cycle is completed and will release0and1. But before it releases the other two threads, it needs to run thebarrierActionyou define as sleeping for 5 seconds, so it does that.You'd then see output
Thread
2still holds the lock, isRUNNABLEand is ready to exit the barrier, so it does that and you see this output.But before thread
2exists, it will signal all other threads waiting on the current barrier. Here is where it gets tricky, theawaitby0and1is done on the sharedCondition. TheConditionis shared across all barrier "generations". So even though the first two threads wereawaiting prior to the second 3 threadslocking, when asignalAllis done, the first generation threads still have to wait their turn to wake up.At this point we have 5 threads in a
BLOCKED(3, 4 & 5) orTIMED_WAITING(0, 1) state. In this example the timing is important of when they block/wait on theLock. If they all happen in order the queue for the critical section would be:So that the next threads released will be
Thread-3, then4and subsequently5. The reason the queue looks like this is because all threads reach thelockat the same time and they all queue up, thread0and1obviously reached it first and so make it into the barrier's critical section, but thenawaitwhile thread2comes in to wake them up, but now0and1get placed on the end of the queue and 3, 4 and 5 will be triggered next.When thread
2leaves the barrier andsignalAllthreads3and4will run and since they are part of the second generation will suspend until thread5comes through and triggers thebarrieraction. This then prints outFinally, thread
5willsignalAllagain and the rest of the threads will wake up complete.In this case you will see thread
5first complete and the rest to follow