This is a smart strategy, but you donβt understand how Sempahore hand out permissions. If you run your code enough time, you will see that it reaches the second step:
Acquiring lock -- 5 Acquiring lock -- 1 1 Releasing lock -- 1 Acquiring lock -- 3 Acquiring lock -- 2 2 Acquiring lock -- 4 Releasing lock -- 2
If you keep rerunning it enough, you really will see that it successfully completes. This is due to the way Semaphore issues permissions. You assume that Semaphore will try to execute the acquire() call as soon as it has enough permissions to do this. If we carefully consider the documentation for Semaphore.aquire(int) , we will see that this is not the case (my attention):
If insufficient permissions are available, then the current thread is disabled for thread planning purposes and remains inactive until ... some other thread calls one of the release methods for this semaphore, the current thread has the assigned permissions nearby , and the number of available permissions satisfies this request.
In other words, Semaphore stores the queue of the pending purchase request and, after each call to .release() , checks only the queue header. In particular, if you turn on fair queuing (set the second argument of the constructor to true ), you will see that even the first step does not occur, because step 5 is the (usually) first in the queue and even a new acquire() call, which may be completed, will queue for other pending calls.
In short, you cannot rely on .acquire() to return as soon as possible, as your code suggests.
Using .tryAcquire() in a loop, you avoid making blocking calls (and therefore impose a lot more workload on Semaphore ), and as soon as the required number of permissions is available, calling tryAcquire() will successfully get them. It works, but wasteful.
Make a list of expectations in the restaurant. Using .aquire() like you enter your name in a list and expect it to be called. This may not be entirely effective, but they will get to you (reasonably reasonably). Imagine if everyone just shouted to the owner: "Do you have a table for n ?"? as often as possible is your tryAquire() loop. It may still work (as in your example), but this, of course, is not the right way to do this.
So what should you do instead? There are a number of useful tools in java.util.concurrent , and it best depends on what exactly you are trying to do. If you see that each thread starts the next thread, I can use BlockingQueue as a synchronization aid, each time pushing the next step into the queue. Then each thread will poll the queue, and if it is not an activated thread, replace the value and wait again.
Here is an example:
public class MultiThreading { public static void main(String[] args) throws Exception{
This prints the following and successfully exits:
Task 5 is now pending... Task 4 is now pending... Task 3 is now pending... Task 2 is now pending... Task 1 is now pending... Task 5 got task 1 instead - re-queuing Task 4 got task 1 instead - re-queuing Task 3 got task 1 instead - re-queuing Task 2 got task 1 instead - re-queuing Finished task 1 Registering task 2 to run next Task 5 got task 2 instead - re-queuing Task 4 got task 2 instead - re-queuing Task 3 got task 2 instead - re-queuing Finished task 2 Registering task 3 to run next Task 5 got task 3 instead - re-queuing Task 4 got task 3 instead - re-queuing Finished task 3 Registering task 4 to run next Task 5 got task 4 instead - re-queuing Finished task 4 Registering task 5 to run next Finished task 5 Registering task 6 to run next