Java Multithreading Series--principle and example of the semaphore semaphore of "Juc lock" 11
Semaphore Introduction
Semaphore is a count semaphore, and its essence is a "shared lock".
The semaphore maintains a semaphore license set. A thread can obtain a semaphore's license by calling acquire (), and the thread can obtain the license when there is a license available in the semaphore, otherwise the thread must wait until a license is available. The thread can release () The semaphore license it holds.
Java concurrency provides two locking modes: shared locks and exclusive locks. The reentrantlock introduced by the front LZ is the exclusive lock. For an exclusive lock, it can only have one thread at a time, while a shared lock is different, allowing multiple threads to hold the lock in parallel and access the shared resource concurrently.
Exclusive lock It uses a pessimistic locking strategy, which is necessary for writing to avoid conflicting monopolies, but is unnecessary for reading because it does not affect the consistency of the data. If a read-only thread acquires an exclusive lock, other read threads can only wait, which in this case limits unnecessary concurrency and reduces throughput. While the shared lock is different, it relaxes the locking condition and employs an optimistic locking mechanism, which allows multiple read threads to access the same shared resource at the same time.
Semaphore, which is described in the API, is a count semaphore. Conceptually, semaphores maintain a set of licenses. If necessary, block each acquire () before the license is available, and then obtain the license. Each release () adds a license that may release a blocked fetch. However, instead of using the actual license object, Semaphore only counts the number of available licenses and takes action accordingly.
Semaphore is typically used to limit the number of threads that can access certain resources (physical or logical). Below LZ take Barber as an example to brief semaphore.
For the sake of simplicity, we assume that there are only three barbers and one receptionist. At first five guests, the receptionist arranged for three guests to have a haircut, the other two had to wait there, and thereafter everyone who came to the barbershop had to wait. After a period of time, after a barber has finished his haircut, the receptionist arranges for another person (fair or unfair mechanism??). ) to get a haircut. here the barber is equivalent to the public resources, the reception is equivalent to the semaphore (Semaphore), the customer is equivalent to the thread.
Further, we determine that the semaphore semaphore is a nonnegative integer (>=1). When a thread wants to access a shared resource, it must first get semaphore, and when Semaphore >0, gets the resource and makes semaphore–1. If the semaphore value = 0, all shared resources are already occupied by other threads, and the thread must wait for other threads to release the resources. When the thread frees the resource, Semaphore is +1;
When the semaphore semaphore = 1 o'clock, it can be used as a mutex. where 0, 1 is the equivalent of its state, when =1 indicates that other threads can get, when =0, exclusive, that is, other threads must wait.
Semaphore List of functions
Creates a Semaphore with the given number of licenses and unfair fairness settings. Semaphore (int permits)//creates Semaphore with the given number of licenses and the given fairness setting. Semaphore (int permits, Boolean fair)//This semaphore gets a license to block threads until a license is provided, otherwise the thread is interrupted. void Acquire ()//The given number of licenses is obtained from this semaphore, the thread is blocked until the license is provided, or the thread has been interrupted. void acquire (int permits)//Gets a license from this semaphore and blocks it before a license is available. void acquireuninterruptibly ()//The given number of licenses is obtained from this semaphore, and threads are blocked until these licenses are provided. void acquireuninterruptibly (int permits)//returns the number of licenses currently available in this semaphore. int availablepermits ()//Gets and returns all licenses that are available immediately. int drainpermits ()//Returns a collection that contains the thread that may be waiting to be fetched. Protected collection<thread> getqueuedthreads ()//Returns the estimated number of threads waiting to be fetched. int getqueuelength ()//query whether the thread is waiting to be fetched. Boolean hasqueuedthreads ()//returns True if the fairness of this semaphore is set to true. Boolean Isfair ()//decreases the number of available licenses based on the specified reduction amount. protected void reducepermits (int reduction)//release a license to return it to the semaphore. void release ()//releases a given number of licenses and returns it to the semaphore. void release (int permits)//returns a string that identifies this semaphore, and the state of the semaphore. String toString ()//The license is obtained from semaphores only if there is one available license for this semaphore at the time of invocation. Boolean tryacquire ()//The license is obtained from this semaphore only if there is a given number of licenses in this semaphore at the time of invocation. Boolean tryacquire (int permits)//If all licenses are available for this semaphore within a given wait time, andIf the current thread is not interrupted, a given number of licenses are obtained from this semaphore. Boolean tryacquire (int permits, long timeout, timeunit unit)//If there is a license available for this semaphore within a given wait time and the current thread is not interrupted, obtain a license from this semaphore. Boolean Tryacquire (long timeout, timeunit unit)
Semaphore data Structure
The UML class diagram for semaphore is as follows:
Can be seen:
(01) Like "Reentrantlock", semaphore also contains the Sync object, Sync is the sync type, and sync is an abstract class that inherits from Aqs.
Sync consists of two subcategories: "Fair semaphore" Fairsync and "non-fair semaphore" Nonfairsync. Sync is an instance of Fairsync, or an instance of Nonfairsync; By default, Sync is Nonfairsync (that is, the default is a non-fair semaphore).
Semaphore Example
1 Import Java.util.concurrent.ExecutorService; 2 Import java.util.concurrent.Executors; 3 Import Java.util.concurrent.Semaphore; 4 5 public class SemaphoreTest1 {6 private static final int sem_max = ten; 7 public static void Main (string[] Ar GS) {8 Semaphore sem = new Semaphore (Sem_max); 9//Create thread pool ten executorservice ThreadPool = Executor S.newfixedthreadpool (3); 11//thread pool perform tasks in Threadpool.execute (new MyThread (SEM, 5)); Threadpool.ex Ecute (New MyThread (SEM, 4)), Threadpool.execute (New MyThread (SEM, 7)), 15//Close pool Threadpool.shut Down ();}18}19 class MyThread extends Thread {The private volatile Semaphore sem; Signal volume of the private int count; Request Semaphore size MyThread (Semaphore sem, int count) {This.sem = sem;26 This.count = count;27} $ public void Run () {31//Get count of licenses from Semaphore (count); 33 34 Thread.Sleep (+), System.out.println (Thread.CurrentThread () getName () + "acquire count=" +count) (Interruptedexception e) {PNS e.printstacktrace (); ().} finally {39//release Returns the given number of licenses to the semaphore. Sem.release (count), System.out.println (Thread.CurrentThread (). GetName () + "release" + Count + ""); 42}43}44}
Run results (one time):
Pool-1-thread-1 acquire Count=5pool-1-thread-2 acquire COUNT=4POOL-1-THREAD-1 release 5pool-1-thread-2 release 4pool-1-thread-3 Acquire count=7pool-1-thread-3 release 7
The results show that the total number of licenses for the SEM is 10, with 3 threads, and the number of semaphore licenses required to be acquired is 5,4,7. After the first two threads have obtained a license to the semaphore, the remaining number of licenses available in the SEM is 1; therefore, the last thread must wait for the first two threads to release the semaphore licenses they hold before acquiring 7 semaphore licenses.
Semaphore Source Analysis (based on jdk1.7.0_40)
Semaphore full source (based on jdk1.7.0_40)
View Code
The semaphore is implemented through shared locks. According to the acquisition principle of shared lock, Semaphore is divided into "fair semaphore" and "non-fair signal quantity".
The difference between "fair semaphore" and "non-fair signal volume"
The mechanism for "fair semaphores" and "non-fair semaphores" is the same as the mechanisms for releasing semaphores! The difference is the mechanism by which they acquire semaphores: When a thread attempts to obtain a semaphore license, it waits for a fair semaphore if the current thread is not in the head of the CLH queue, and for an unfair semaphore, it directly acquires the semaphore regardless of whether the current thread is in the header of the CLH queue. The difference is specific in that their tryacquireshared () functions are implemented differently.
"Fair semaphore" class
View Code
"Non-fair semaphore" class
View Code
Below, we gradually analyze their source code.
1. Signal Volume constructor
public Semaphore (int permits) { sync = new Nonfairsync (permits);} public Semaphore (int permits, Boolean fair) { sync = fair? New Fairsync (Permits): New Nonfairsync (permits);}
From this, we can divide the signal into "fair Semaphore (Fairsync)" and "non-fair Signal volume (Nonfairsync)". The Semaphore (int permits) function creates a "non-fair semaphore" by default.
2. Fair Semaphore Acquisition and release
2.1 Acquisition of fair signal volume
The fair signal amount in semaphore is fairsync. Its fetch API is as follows:
public void Acquire () throws Interruptedexception { sync.acquiresharedinterruptibly (1);} public void acquire (int permits) throws Interruptedexception { if (permits < 0) throw new IllegalArgumentException ( ); sync.acquiresharedinterruptibly (permits);}
The acquire () in the semaphore gets the function, which is actually the acquiresharedinterruptibly () in the called Aqs.
The source code of acquiresharedinterruptibly () is as follows:
Public final void acquiresharedinterruptibly (int arg) throws interruptedexception { //If the thread is in a break state, an exception is thrown. if (thread.interrupted ()) throw new Interruptedexception (); Otherwise, an attempt is made to obtain a "shared lock", a success is returned directly, and a failure is obtained by doacquiresharedinterruptibly (). if (tryacquireshared (ARG) < 0) doacquiresharedinterruptibly (ARG);}
The tryacquireshared () corresponding to the "fair lock" in semaphore is implemented as follows:
protected int tryacquireshared (int acquires) {for (;;) { //To determine if the "current thread" is the first thread thread in the CLH queue, ///If it returns-1. if (hasqueuedpredecessors ()) return-1; Set "number of allowed semaphores" int available = GetState (); Set "After obtaining acquires Semaphore license, the remaining Semaphore license number" int remaining = Available-acquires; If the remaining semaphore licenses >=0, set the number of semaphore licenses available to remaining. if (Remaining < 0 | | Compareandsetstate (available, remaining)) return remaining; }}
Description : The Role of tryacquireshared () is to attempt to acquire acquires number of semaphores.
For semaphore, state represents "the number of semaphore licenses currently available".
Here's a look at the implementation of doacquiresharedinterruptibly () in Aqs:
private void doacquiresharedinterruptibly (Long arg) throws Interruptedexception {//Creates node node for "Current thread" and locks recorded in node is a "shared lock" type, and the node is added to the end of the CLH queue. Final node node = addwaiter (node.shared); Boolean failed = true; try {for (;;) {//Gets the previous node. If the previous node is the header of the CLH queue, "attempt to acquire a shared lock." Final Node p = node.predecessor (); if (p = = head) {Long r = tryacquireshared (ARG); if (r >= 0) {setheadandpropagate (node, r); P.next = null; Help GC failed = false; Return }}//When the front thread waits until the shared lock is acquired. If the thread is interrupted during the wait, the thread is interrupted again (the interrupt state before the restore). if (Shouldparkafterfailedacquire (p, node) && parkandcheckinterrupt ()) throw new Inte Rruptedexception (); }} finally {if (failed) cancelacquire (node); }}
description : doacquiresharedinterruptibly () causes the current thread to wait until the current thread acquires a shared lock (or is interrupted) to return.
Addwaiter (node.shared) is the role of creating node node for "Current thread", and the type of lock recorded in node is "shared lock" (node.shared), and the node is added to the end of the CLH queue. About node and CLH in the "Java Multithreading Series--" Juc lock "03 of the Fair Lock (a)" has been described in detail, here will not repeat the explanation.
(Node.predecessor) The function of the () is to get the previous node. If the previous node is the header of the CLH queue, "attempt to acquire a shared lock."
Shouldparkafterfailedacquire (), like its name, returns true if the thread should wait after the attempt to acquire the lock fails, otherwise false.
(04) when Shouldparkafterfailedacquire () returns ture, Parkandcheckinterrupt () is called, and the current thread goes into a wait state until the shared lock is acquired to continue running.
Doacquiresharedinterruptibly () in the Shouldparkafterfailedacquire (), Parkandcheckinterrupt and other functions in the "Java Multithreading Series-" Juc lock "03 Fair Lock (i) "introduced, here is no longer detailed description.
2.2 Release of Fair signal volume
The release API for Fair Semaphore (Fairsync) in Semaphore is as follows:
public void Release () { sync.releaseshared (1);} public void release (int permits) { if (permits < 0) throw new IllegalArgumentException (); Sync.releaseshared (permits);}
The Semaphore's releases () release function is actually called the releaseshared () in the Aqs.
Releaseshared () implemented in Aqs, the source code is as follows:
Public final Boolean releaseshared (int arg) { if (tryreleaseshared (ARG)) { doreleaseshared (); return true; } return false;}
Description : The purpose of releaseshared () is to have the current thread release the shared lock it holds.
It will first attempt to release the shared lock through tryreleaseshared (). If the attempt succeeds, it returns directly; if the attempt fails, the shared lock is released through Doreleaseshared ().
Semaphore rewrite the tryreleaseshared (), its source code is as follows:
Protected Final Boolean tryreleaseshared (int releases) {for (;;) { //get "number of allowed semaphores" int current = GetState (); Get "The number of remaining semaphore licenses after releasing releases semaphores" int next = current + releases; if (Next < current)//overflow throw new Error ("Maximum Permit count Exceeded"); Set "number of allowed semaphores" to next. if (compareandsetstate (current, next)) return true; }}
If tryreleaseshared () attempts to release a shared lock fails, doreleaseshared () is called to release the shared lock. The source code of Doreleaseshared () is as follows:
private void doreleaseshared () {for (;;) { //Get the head node of the CLH queue nodes h = head; If the head node is not NULL, and the head node is not equal to the tail node. if (h! = NULL && h! = tail) { //Gets the state of the thread corresponding to the head node int ws = H.waitstatus; If the thread corresponding to the head node is the signal state, it means that the thread corresponding to the next node of the head node needs to be awakened by Unpark. if (ws = = node.signal) { //sets the thread state corresponding to the head node is empty. If it fails, the loop continues. if (!compareandsetwaitstatus (H, node.signal, 0)) continue; Wakes the thread corresponding to the next node of the head node. unparksuccessor (h); } If the thread corresponding to the head node is empty, set the shared lock owned by the corresponding thread of the file point to get the empty state of the lock for the other thread. else if (ws = = 0 && !compareandsetwaitstatus (H, 0, node.propagate)) continue; Loop on failed CAS } //If the head node changes, the loop continues. Otherwise, exit the loop. if (h = = head) //loop If head changed break ; }}
description : doreleaseshared () releases "shared lock". It will traverse the CLH queue from the go, "Wake up" and then "execute" the corresponding thread for each node in the queue, and the ultimate goal is to let these threads release the semaphore they hold.
3 Non-fair semaphore acquisition and release
The non-fair semaphore in Semaphore is nonfairsync. In Semaphore, the "release of the non-fair Semaphore license" is the same as the release of the Fair Semaphore license.
The difference is that they acquire the "semaphore license" mechanism differently, below is the code of the non-fair semaphore to get the semaphore license.
The tryacquireshared () of the non-fair semaphore is implemented as follows:
protected int tryacquireshared (int acquires) { return nonfairtryacquireshared (acquires);}
The implementation of nonfairtryacquireshared () is as follows:
final int nonfairtryacquireshared (int acquires) {for (;;) { //set "number of allowed semaphores" int available = GetState (); Set "After obtaining acquires Semaphore license, the remaining Semaphore license number" int remaining = Available-acquires; If the remaining semaphore licenses >=0, set the number of semaphore licenses available to remaining. if (Remaining < 0 | | Compareandsetstate (available, remaining)) return remaining; }}
description : The tryacquireshared () of the non-fair semaphore calls the Nonfairtryacquireshared () in Aqs. In the For loop of the nonfairtryacquireshared (), it will directly determine if the "current number of semaphore licenses remaining" is sufficient, and if sufficient, "set the number of semaphores allowed" to obtain the semaphore.
In the tryacquireshared () of the fair Semaphore, the "current thread is not the head of the CLH queue" is determined by the IF (Hasqueuedpredecessors ()) before the semaphore is obtained, and 1 is returned.
Java-"JUC" Semaphore source analysis