Semaphore is a count semaphore. In terms of concept, semaphores maintain a set of licenses ). The acquire method is congested when necessary until a license is available and then the license is taken away. Adding a license to the release method may release an acquirer that is blocked ). However, semaphore does not use a real license object, but only keeps a usable count and returns the corresponding behavior.
Semaphores are generally used to limit the number of concurrent threads of resources that can be asked physically or logically.
When the semaphore is initialized to 1, it means that it has only one consent available at most, so that it can be used as an exclusive lock for mutual exclusion. Many of these are called binary semaphore because they only have two States: one license is available, or zero license is available. When this method is used, binary semaphores have this attribute (unlike the implementation of most locks): the lock can be owned (just as the semaphores have no owner concept) other threads are released. Such attributes are very useful in some special contexts, such as deadlock recovery.
The constructor of the class optional accepts a fairness vertex number. When it is set to false, this class does not guarantee the thread's license order. In particular, the plug-in agrees, that is, the thread can call the acquire method to assign a license before another waiting thread-the new thread logically places itself in the waiting thread queue header. When fairness is set to true, the semaphore ensures that the threads that call the acquire method will obtain the permission (FIFO) in the order they call the method ). Note that the FIFO order determines the internal running points of these methods. Therefore, it is possible that a thread calls the acquire method before another thread, but the actual order will be after another thread, the same also applies to the order returned by the function. Note that the tryacquire method of the non-Timeout version does not follow the fairness settings and immediately obtains any available licenses.
In general, if semaphores are used to control the resource issue, they should be initialized to Fair (fairness is set to true), so as to ensure that no thread will be hungry when the issue resource. When semaphores are used for other synchronization controls, the unfair throughput advantage is generally better than the fair one.
In fact, the concept of the number of fairness metrics and the number of States is very similar to that provided by AQS (abstractqueuedsynchronizer, therefore, we should also guess that the internal implementation of semaphore also implements interface functions through an internal class inheriting AQS. Next, let's take a closer look at the internal implementation.
Detailed Implementation
Let's take a look at the constructor of semaphore:
public Semaphore(int permits) { sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }We can see that the constructor is similar to the reentrantlock implementation. They all create different lock classes based on the number of fair shards. Let's take a look at the implementation of the acquire and release interfaces of semaphore.
public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public void release() { sync.releaseShared(1); }We can see that the implementation of acquire and release is to call the internal method of sync. Of course, these methods are the interfaces provided by AQS to obtain and release shared locks. Next, let's take a look at the most basic internal-class sync implementation in the entire implementation:
Abstract static class sync extends actqueuedsynchronizer {Private Static final long serialversionuid = synchronized; sync (INT permits) {setstate (permits);} final int nonfairtryacquireshared (INT acquires) {(;;) {int available = getstate (); int remaining = available-acquires; If (remaining <0 | compareandsetstate (available, remaining) return remaining ;}} protected final Boolean tryreleaseshared (INT releases) {for (;) {int current = getstate (); int next = Current + releases; If (next <current) // overflow throw new error ("Maximum permit count exceeded"); If (compareandsetstate (current, next) return true ;}} // omit some secondary methods}/*** unfair version number */static final class nonfairsync extends sync {Private Static final long serialversionuid =-2694183684443567898l; nonfairsync (INT permits) {super (permits);} protected int tryacquireshared (INT acquires) {return nonfairtryacquireshared (acquires );}} /*** fair version number */static final class fairsync extends sync {Private Static final long serialversionuid = 2014338818796000944l; fairsync (INT permits) {super (permits );} protected int tryacquireshared (INT acquires) {for (;) {If (hasqueuedpredecessors () Return-1; int available = getstate (); int remaining = available-acquires; if (remaining <0 | compareandsetstate (available, remaining) return remaining ;}}}To facilitate understanding of the main logic, the sync class omitted some secondary methods. Non-fair version nonfairsync and fair version are both inherited from the sync class, the sync class is inherited from the AQS class, And the nonfairsync and fairsync classes both have the same tryreleaseshared implementation, it is only slightly different in the implementation of tryacquireshared.
Let's take a look at the non-fair nonfairsync implementation. Tryacquireshared calls the nonfairtryacquireshared method of the sync class. The implementation of the method is quite simple. It is only after the current lock status value is inferred within the loop minus the request value acquires, if remaining <0 (this indicates that the acquire fails and a negative value of remaining is returned directly) or if remaining> = 0, compareandsetstate is successful (this indicates that the acquire is successful, directly return a remaining value greater than or equal to 0). If CAS fails, repeat the loop until one of the cases occurs.
Let's take a look at the implementation of fair-class fairsync. Tryacquireshared is directly overwritten. In contrast to the non-fair version number, hasqueuedpredecessors is added to infer that the method in AQS indicates whether a node is in front of itself in the current waiting queue. If true is returned, it indicates that the current thread needs to enter the waiting queue, and-1 is returned directly, indicating that acquire failed.
The implementation of tryreleaseshared is also very easy. It is also a loop in which CAS continuously adds the request releases to the lock status.
Semaphore also has some other auxiliary methods. In fact, it is also a simple method to call internal-class sync, so we will not go into details here.
Summary
In general, the lock status value of AQS is equal to the license volume of semaphore. The implementation of acquire is to remove the current lock status value, that is, the license volume, and the corresponding value, the implementation of release is to add the corresponding value to the lock status value. The entire implementation structure is similar to reentrantlock, but there is no re-import logic, and the implementation is relatively simple, so it should be easy to understand.
Semaphore implements source code analysis for andoird