Java concurrency series [6] ---- Semaphore source code analysis, java ---- semaphore
Semaphore is a common class in the JUC package. It is an application in the AQS sharing mode and allows multiple threads to operate on shared resources at the same time, in addition, it can effectively control the number of concurrent jobs and use it to control traffic. Semaphore provides the concept of a license, which can be considered as a bus ticket. Only those who have successfully obtained the ticket can get on the bus, and there are a certain number of tickets, it is impossible to send it without limits, which will lead to overload of buses. So when the ticket is delivered (the bus is fully loaded), other people can only wait for the next bus. If someone gets off the bus halfway, his location will be free, so if someone else wants to get on the bus, he will be able to get the ticket again. We can use Semaphore to implement various pools. At the end of this article, we will write a simple database connection pool. First, let's take a look at the Semaphore constructor.
1 // constructor 12 public Semaphore (int permits) {3 sync = new NonfairSync (permits); 4} 5 6 // constructor 27 public Semaphore (int permits, boolean fair) {8 sync = fair? New FairSync (permits): new NonfairSync (permits); 9}
Semaphore provides two parameter-free constructors. Both constructors must pass in an initial License Quantity. semaphores constructed by constructor 1 are obtained in an unfair way when obtaining a license, you can use the constructor 2 to specify the way to obtain the license through parameters (fair or unfair ). Semaphore provides two types of APIS for obtaining and releasing licenses. By default, Semaphore obtains and releases a license, and can also input parameters to simultaneously obtain and release multiple licenses. In this article, we will only discuss how to obtain and release a license each time.
1. obtain a license
1 // obtain a license (Response interrupted) 2 public void acquire () throws InterruptedException {3 sync. acquireSharedInterruptibly (1); 4} 5 6 // get a license (no response to interruption) 7 public void acquireUninterruptibly () {8 sync. acquireShared (1); 9} 10 11 // try to obtain a license (unfair access) 12 public boolean tryAcquire () {13 return sync. nonfairTryAcquireShared (1)> = 0; 14} 15 16 // try to get a license (timed acquisition) 17 public boolean tryAcquire (long timeout, TimeUnit unit) throws InterruptedException {18 return sync. tryAcquireSharedNanos (1, unit. toNanos (timeout); 19}
The above API is the default license acquisition operation provided by Semaphore. Only one license is obtained each time, which is a common situation in real life. In addition to direct retrieval, direct retrieval may block the thread after a failure, but it does not. Note that the tryAcquire method is obtained in an unfair way. In normal times, we usually use the acquire method to obtain a license. Let's take a look at how it is obtained. We can see that the acquire method directly calls sync. acquireSharedInterruptibly (1). This method is the method in AQS. We have discussed it in the AQS source code series. Now let's review it.
1 // obtain the lock (share mode) in an interrupt mode 2 public final void acquireSharedInterruptibly (int arg) throws InterruptedException {3 // first judge whether the thread is interrupted, if yes, an exception is thrown. 4 if (Thread. interrupted () {5 throw new InterruptedException (); 6} 7 // 1. try to get the lock 8 if (tryAcquireShared (arg) <0) {9 // 2. if the retrieval fails, enter the method 10 doAcquireSharedInterruptibly (arg); 11} 12}
The acquireSharedInterruptibly method first calls the tryAcquireShared method to try to obtain it. tryAcquireShared is an abstract method in AQS. The two Derived classes FairSync and NonfairSync implement the logic of this method. FairSync implements the fair access logic, while NonfairSync implements the unfair access logic.
1 abstract static class Sync extends synchronized actqueuedsynchronizer {2 // unfair way to try to get 3 final int nonfairTryAcquireShared (int acquires) {4 (;;) {5 // obtain the available license 6 int available = getState (); 7 // obtain the remaining license 8 int remaining = available-acquires; 9 // 1. if remaining is less than 0, the system returns remaining10 // 2. if the remaining value is greater than 0, update the synchronization status first and then return the remaining11 if (remaining <0 | compareAndSetState (available, remaining) {12 return remaining; 13} 14} 15} 16} 17 18 // unfair synchronizator 19 static final class NonfairSync extends Sync {20 private static final long serialVersionUID =-2694183684443567898L; 21 22 NonfairSync (int permits) {23 super (permits); 24} 25 26 // try to get license 27 protected int tryAcquireShared (int acquires) {28 return nonfairTryAcquireShared (acquires ); 29} 30} 31 32 // fair synchronization 33 static final class FairSync extends Sync {34 private static final long serialVersionUID = 2014338818796000944L; 35 36 FairSync (int permits) {37 super (permits); 38} 39 40 // try to obtain the license 41 protected int tryAcquireShared (int acquires) {42 (;;) {43 // determine whether there is a queue before the synchronization queue 44 if (hasQueuedPredecessors () {45 // if yes, the system returns-1 directly, indicating that the attempt to obtain the result failed 46 return-1; 47} 48 // obtain the available license 49 int available = getState (); 50 // obtain the remaining license 51 int remaining = available-acquires; 52 // 1. if remaining is less than 0, the system returns remaining53 // 2. if the remaining value is greater than 0, update the synchronization status first and then return remaining54 if (remaining <0 | compareAndSetState (available, remaining) {55 return remaining; 56} 57} 58} 59}
Note that the tryAcquireShared method of NonfairSync directly calls the nonfairTryAcquireShared method, which is in the parent Sync class. The logic for unfair lock acquisition is to first retrieve the current synchronization status (the synchronization status indicates the number of licenses), and subtract the current synchronization status from the parameters of the parameter, if the result is not less than 0, it indicates that there is still a license available, then you can directly use CAS to update the synchronization status, and finally return this result no matter whether the result is less than 0. Here, we need to understand the meaning of the return value of the tryAcquireShared method. If the return value is negative, the retrieval fails. If the return value is zero, the current thread gets the result successfully, but the subsequent thread cannot obtain the result, A positive number indicates that the current thread is successfully obtained and can be obtained by subsequent threads. Let's take a look at the code of the acquireSharedInterruptibly method.
1 // obtain the lock (share mode) in an interrupt mode 2 public final void acquireSharedInterruptibly (int arg) throws InterruptedException {3 // first judge whether the thread is interrupted, if yes, an exception is thrown. 4 if (Thread. interrupted () {5 throw new InterruptedException (); 6} 7 // 1. attempt to get the lock 8 // negative number: indicates that the acquisition failed 9 // zero value: indicates that the current thread has succeeded in obtaining the lock, but the subsequent thread cannot obtain 10 // positive number: indicates that the current thread is successfully obtained, and the subsequent thread can also obtain the result. 11 if (tryAcquireShared (arg) <0) {12 // 2. if the retrieval fails, enter the Method 13 doAcquireSharedInterruptibly (arg); 14} 15}
If the returned remaining is less than 0, the retrieval fails. Therefore, tryAcquireShared (arg) <0 is true, so the doAcquireSharedInterruptibly method will be called next. We will talk about AQS in this method, it encapsulates the current thread as a node and puts it at the end of the synchronization queue, and may suspend the thread. This is also the reason why the thread will be blocked when the remaining is less than 0. If the returned remaining> = 0, it indicates that the current thread is successfully obtained. Therefore, if tryAcquireShared (arg) <0 is flase, the method doAcquireSharedInterruptibly is no longer called to block the current thread. The above is the entire unfair access logic, while the fair access is only prior to this, the hasQueuedPredecessors method is called to determine whether a synchronization queue is in the queue. If yes, return-1 indicates that the acquisition failed, otherwise, the following steps will be continued.
2. Release the license
1 // release a license 2 public void release () {3 sync. releaseShared (1); 4}
The call to the release method is to release a license, and its operation is very simple. It calls the releaseShared method of AQS. Let's take a look at this method.
1 // unlock operation (share mode) 2 public final boolean releaseShared (int arg) {3 // 1. try to release lock 4 if (tryReleaseShared (arg) {5 // 2. 6 doReleaseShared (); 7 return true; 8} 9 return false; 10}
The releaseShared method of AQS first calls the tryReleaseShared method to try to release the lock. The implementation logic of this method is in the sub-class Sync.
1 abstract static class Sync extends actqueuedsynchronizer {2... 3 // try to release 4 protected final boolean tryReleaseShared (int releases) {5 for (;) {6 // get the current synchronization Status 7 int current = getState (); 8 // Add the input parameter 9 int next = current + releases to the current synchronization status; 10 // if the addition result is smaller than the current synchronization status, an error occurs: 11 if (next <current) {12 throw new Error ("Maximum permit count exceeded"); 13} 14 // update the value of synchronization status in CAS mode. If the update is successful, true is returned, otherwise, the loop will continue 15 if (compareAndSetState (current, next) {16 return true; 17} 18} 19} 20... 21}
You can see that the tryReleaseShared method uses the for loop for spin. First, get the synchronization status, add the synchronization status with the passed parameters, and then update the synchronization status in CAS mode, if the update is successful, true is returned and the method is displayed. Otherwise, the loop continues until the update is successful. This is the process of releasing the license.
3. Write a connection pool
The Semaphore code is not very complex. A common operation is to obtain and release a license. The implementation logic of these operations is also relatively simple, but this does not hinder the wide application of Semaphore. Next we will use Semaphore to implement a simple database connection pool. Through this example, we hope that readers can have a better understanding of the use of Semaphore.
1 public class ConnectPool {2 3 // connection pool size 4 private int size; 5 // database connection set 6 private Connect [] connects; 7 // connection status flag 8 private boolean [] connectFlag; 9 // remaining number of available connections 10 private volatile int available; 11 // Semaphore 12 private semaphore Semaphore; 13 14 // constructor 15 public ConnectPool (int size) {16 this. size = size; 17 this. available = size; 18 semaphore = new Semaphore (size, true); 19 connects = new Connect [size]; 20 conn EctFlag = new boolean [size]; 21 initConnects (); 22} 23 24 // initialize the connection 25 private void initConnects () {26 // generate a specified number of database connections 27 for (int I = 0; I <this. size; I ++) {28 connects [I] = new Connect (); 29} 30} 31 32 // get the database connection 33 private synchronized Connect getConnect () {34 for (int I = 0; I <connectFlag. length; I ++) {35 // traverse the set to find unused connections 36 if (! ConnectFlag [I]) {37 // set the connection to 38 connectFlag [I] = true; 39 // The number of available connections minus 140 available --; 41 System. out. println ("[" + Thread. currentThread (). getName () + "] to obtain the number of remaining connections:" + available); 42 // return the connection reference 43 return connects [I]; 44} 45} 46 return null; 47} 48 49 // get a connection 50 public Connect openConnect () throws InterruptedException {51 // get a license 52 semaphore. acquire (); 53 // get database connection 54 return getConnect (); 55} 56 57 // release a connection 58 public synchronized void release (Connect connect Connect) {59 for (int I = 0; I <this. size; I ++) {60 if (connect = connects [I]) {61 // set the connection to 62 connectFlag [I] = false; 63 // number of available connections plus 164 available ++; 65 System. out. println ("[" + Thread. currentThread (). getName () + "] to release the remaining connections:" + available); 66 // release the license 67 semaphore. release (); 68} 69} 70} 71 72 // number of available connections 73 public int available () {74 return available; 75} 76 77}
Test code:
1 public class TestThread extends Thread {2 3 private static ConnectPool pool = new ConnectPool (3); 4 5 @ Override 6 public void run () {7 try {8 Connect connect = pool. openConnect (); 9 Thread. sleep (100); // take a rest for 10 pools. release (connect); 11} catch (InterruptedException e) {12 e. printStackTrace (); 13} 14} 15 16 public static void main (String [] args) {17 for (int I = 0; I <10; I ++) {18 new TestThread (). start (); 19} 20} 21 22}
Test results:
We use an array to store database connection references. When initializing the connection pool, we call the initConnects method to create a specified number of database connections and store their references in the array, in addition, there is an array of the same size to record whether the connection is available. When an External Thread requests a connection, it first calls semaphore. acquire () to obtain a license, sets the connection status to in use, and finally returns a reference to the connection. The number of licenses is determined by the parameters passed in during the construction. semaphore is called every time. the number of licenses in the acquire () method is reduced by 1. If the number is reduced to 0, no connection is available. If other threads attempt to obtain the license again, it will be blocked. When a thread releases a connection, semaphore is called. release () releases the license, and the total number of licenses increases again, indicating that the number of available connections has increased. The previously blocked thread will wake up to continue obtaining connections, in this case, the connection will be successfully obtained after obtaining the connection again. In the test example, a connection pool with three connections is initialized. We can see from the test results that each time the thread obtains a connection, the remaining connections will be reduced by 1, when the value is reduced to 0, other threads cannot obtain the data again. At this time, you must wait for a thread to release the connection before obtaining the data again. We can see that the number of remaining connections always changes between 0 and 3, which indicates that this test was successful.