Java Semphore Details and Source Analysis

What is a semaphore Semphore s are used to co...
Semphore implementation ideas
Semphore Source Analysis
Example
What is a semaphore

Semphore s are used to control the number of operations that simultaneously access a particular resource or perform a specified operation at the same time to ensure the rational use of a limited resource.

Semphore implementation ideas

Semphore is implemented based on an AQS queue synchronizer, which manages a set of virtual licenses that are initialized through Semphore's constructor. Before the program can perform the specified operation, it needs to try to obtain a license. If there are no remaining licenses, the current thread is blocked until it has a licensed location. When the operation is completed, a license is released. When the initial license value is set to 1, it can be used as a non-reentrant mutex (which is not difficult to understand after reading).

Semphore Source Analysis

This part ignores some unrelated codes, and omits parts are uniformly expressed with'//...', only combing the main function flow ideas.

Construction methods and static internal classes

Semphore provides both fair and unfair policies. The difference is reflected in the different strategies when trying to obtain licenses. The classes NonfairSync and FairSync within Semphore are used to implement two different strategies. First, focus on the construction method of Semphore and the static internal classes Sync, NonfairSync, FairSync. The code is as follows:

private final Sync sync; //Thus Semphore is implemented based on AQS abstract static class Sync extends AbstractQueuedSynchronizer { /** * Sync The construction method used to set the specified license * setState()The value set by the method is a volatile variable to ensure visibility between threads */ Sync(int permits) { setState(permits); } final int getPermits() { return getState(); } //The following methods omit subsequent analysis final int nonfairTryAcquireShared(int acquires) { //... } protected final boolean tryReleaseShared(int releases) { //... } final void reducePermits(int reductions) { //... } final int drainPermits() { //... } } static final class NonfairSync extends Sync { NonfairSync(int permits) { super(permits); } protected int tryAcquireShared(int acquires) { return nonfairTryAcquireShared(acquires); } } static final class FairSync extends Sync { private static final long serialVersionUID = 2014338818796000944L; FairSync(int permits) { super(permits); } protected int tryAcquireShared(int acquires) { //... } } /** * Semaphore Two construction methods are provided * Where the permits parameter of type int is used to specify the initial number of licenses * The boolean fairparameter is used to select which fairness policy the current Semaphore will adopt * As you can see from the first construction method, the default strategy is an unfair one */ public Semaphore(int permits) { sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }

The internal class inheritance relationship is as follows:

In addition to the in-code comments, it is important to note that the key to the implementation of different strategies by the two subclasses of Sync is the override of the tryAcquireShared() method, in which the implementation logic is written directly within the fair policy rather than calling the nonfairTryAcquireShared() method in the parent Sync, where the implementation logic of unfair policies is reflected.

The benefit of this is that no matter which strategy you choose, users don't need to care about how it's implemented internally, as long as they call the acquire() (license acquisition method, analysis below) method directly.

Logical process for obtaining and releasing licenses

Next, focus on Semaphore's license acquisition methods and release methods.
Focus first on methods to obtain licenses

Obtain a license

First, focus on the most commonly used methods to obtain licenses, acquire(), and related core implementations:

/** * This method is a member of Semaphore by * The acquireSharedInterruptibly method was called within the method * The default incoming parameter 1 indicates that the current thread wants a license */ public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); } //Not much different from the no-parameter acquire() except that the number of licenses to be acquired by the current thread is set public void acquire(int permits) throws InterruptedException { if (permits < 0) throw new IllegalArgumentException(); sync.acquireSharedInterruptibly(permits); } public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); /** * Here's a look at the tryAcquireShared() method * Since Semaphore has two strategies, the next two code blocks are analyzed separately */ if (tryAcquireShared(arg) < 0) /** * This method is a member method of AbstractQueuedSynchronizer * Content does not expand what you do in this section of analysis * Block the current thread if the if condition is met until sufficient permissions are available to supply the current thread */ doAcquireSharedInterruptibly(arg); }

The tryAcquireShared method under the unfair policy and the follow-up process are as follows:

//The following two behaviors are part of the acquireSharedInterruptibly method that focuses on criteria if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); //This method is a member method of NonfairSync responsible for calling the parent method nonfairTryAcquireShared() protected int tryAcquireShared(int acquires) { return nonfairTryAcquireShared(acquires); } //This method is a member method of Sync final int nonfairTryAcquireShared(int acquires) { for (;;) { /** * Variable understanding is simple * available Indicates the current remaining licenses * remaining Indicates if the license surplus is allocated */ int available = getState(); int remaining = available - acquires; /** * Focus first on the first condition * If the license remaining is less than 0, no CAS operation is required * Return negative directly to the call causing the current thread to block * Indicates that the current remaining licenses cannot meet the current thread's current thread needs to be blocked * Focus on the second condition again * CAS operations when the current license meets the current thread requirements * If the CAS update fails, other threads have also made changes to the licensed remaining classes * Re-execute the current loop to re-judge * If the CAS update succeeds, the current thread successfully acquires licenses and the remaining licenses are successfully updated * Returning a positive remaining does not cause thread blocking */ if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }

The following are the tryAcquireShared methods and follow-up processes under the Fair Policy:

//This method is a member method of FairSync protected int tryAcquireShared(int acquires) { for (;;) { /* * The difference with unfair policy is that fair policy checks blocked queues and the rest of the processes are identical * Participate in licensing competition if the blocking queue has no waiting threads * Otherwise, insert directly into the end of the blocked queue and suspend, waiting to be waked up * The specific judgment within the method is not expanded */ if (hasQueuedPredecessors()) return -1; int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }

In addition to the two acquisition methods described above, Semphore provides three other acquisition methods, coded as follows:

//acquire() is an infinite time, interruptible, and blocking method for obtaining licenses /** * acquireUninterruptibly()Is an infinite time, uninterrupted, and blocked access to licenses * First, if the thread is blocking by calling acquire * Threads are threads that can be interrupted and throw an InterruptedException exception when interrupted * And stop waiting for permission to continue * acquireUninterruptibly, as its name implies, is uninterruptible * Threads that call this method and enter the interrupted state can exit the blocked state only if they are licensed * The remaining implementations are identical to acquire */ public void acquireUninterruptibly() { sync.acquireShared(1); } //There is a custom license acquisition version public void acquireUninterruptibly(int permits) { if (permits < 0) throw new IllegalArgumentException(); sync.acquireShared(permits); } /** * tryAcquire Is a method of obtaining licenses for an unlimited amount of time without blocking access * Threads that invoke this method will only attempt to obtain a license once * Will not be blocked regardless of success * Return true after successful license acquisition and false otherwise * The remaining implementations are identical to acquire */ public boolean tryAcquire() { return sync.nonfairTryAcquireShared(1) >= 0; } //There is also a custom license acquisition version public boolean tryAcquire(int permits) { if (permits < 0) throw new IllegalArgumentException(); return sync.nonfairTryAcquireShared(permits) >= 0; } /** * tryAcquire(long timeout, TimeUnit unit)Is a time-limited, interruptible, and blocking method for obtaining licenses * The difference between this method and acquire is the time limit * The current thread will be blocked if no license is obtained within the time limit * Stop waiting for execution to continue if time exceeds * The remaining implementations are identical to acquire */ public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } //There is also a custom license acquisition version public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { if (permits < 0) throw new IllegalArgumentException(); return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout)); }
Release License

Next, focus on the method release() used to release licenses and related core implementations:

//release() is a member method of Semphore that releases a license by default public void release() { sync.releaseShared(1); } //Release the specified license quantity version public void release(int permits) { if (permits < 0) throw new IllegalArgumentException(); sync.releaseShared(permits); } //releaseShared(int arg) is a member method of AbstractQueuedSynchronizer public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { /** * This method is a member method of AbstractQueuedSynchronizer * Content does not expand what you do in this section of analysis * Release if if the if condition is met */ doReleaseShared(); return true; } return false; } protected final boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (next < current) throw new Error("Maximum permit count exceeded"); /** * Release logic is easy to understand and only one CAS operation is used to guarantee the atomicity of the update operation * Return true when remaining licenses are successfully updated for subsequent true release operations */ if (compareAndSetState(current, next)) return true; } }

Other methods

In addition to the main process, other methods are described:

//Return the current number of remaining licenses public int availablePermits() { return sync.getPermits(); } /** * Get all remaining licenses and return the number of licenses you have obtained * Release licenses can be used with release(int permits) */ public int drainPermits() { return sync.drainPermits(); } //Reduce the number of licenses protected void reducePermits(int reduction) { if (reduction < 0) throw new IllegalArgumentException(); sync.reducePermits(reduction); } //Judging Current Equity Policy public boolean isFair() { return sync instanceof FairSync; } //Determine if there are threads waiting to be licensed on the current Semaphore instance public final boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } //Returns the number of threads waiting to be licensed on the current Semaphore instance public final int getQueueLength() { return sync.getQueueLength(); } //Return to the thread waiting for permission on the current Semaphore instance protected Collection<Thread> getQueuedThreads() { return sync.getQueuedThreads(); }

That's what Semphore is all about source analysis

Example

1. Use Semphore to change the container into a bounded blocking container

Note: The example is from the Java Concurrent Programming Actual Warfare program list 5-14
The code is easy to understand, just add some comments, no testing here

public class BoundedHashSet<T> { private final Set<T> set; private final Semaphore sem; //The bound here is actually the maximum container capacity public BoundedHashSet(int bound) { this.set = Collections.synchronizedSet(new HashSet<>()); sem = new Semaphore(bound); } public boolean add(T o) throws InterruptedException{ //You must obtain a license before adding elements to the container //At the same time, acquire will be blocked if there is no remaining capacity sem.acquire(); boolean wasAdded = false; try{ wasAdded = set.add(o); return wasAdded; }finally { //Guarantee release of license if add fails if(!wasAdded) sem.release(); } } public boolean remove(Object o){ boolean wasRemoved = set.remove(o); if(wasRemoved) //Remove element successfully releases a license sem.release(); return wasRemoved; } }

2. Simulate database connection pools using Semphore

The following code ideas come from https://blog.csdn.net/cy_Alone/article/details/70193658, made some minor changes here, code as follows:

public class ConnectPool { //Connection id private static int connectId = 1; //Connection pool size/number of licenses within Semaphore private int size; private Vector connects; private boolean [] isConnectionUsed; private final Semaphore semaphore; static final class Connect{ //Connection unique id private final int id = connectId++; public Connect() { try { //Analog connection startup time-consuming Thread.sleep(500); } catch (InterruptedException e) { } System.out.println("Connect" + id + "Start Successfully"); } } public ConnectPool(int size) { this.size = size; semaphore = new Semaphore(size); connects = new Vector(); isConnectionUsed = new boolean[size]; initConnects(); } //Initialize database connection pool private void initConnects(){ for (int i = 0; i < size; i++) { connects.add(new Connect()); } } //Attempt to get a database connection public Connect tryAcquireConnect() throws InterruptedException{ semaphore.acquire(); return acquireConnect(); } private synchronized Connect acquireConnect(){ for (int i = 0; i < size; i++) { if(!isConnectionUsed[i]){ //Mark that the connection is already in use isConnectionUsed[i] = true; return (Connect) connects.get(i); } } return null; } //Release a database connection public synchronized void releaseConnect(Connect connect){ for (int i = 0; i < size; i++) { if(connect==connects.get(i)){ isConnectionUsed[i] = false; semaphore.release(); } } } }

The test code is as follows:

public static void main(String[] args) { //Setting 1 here becomes a mutex in other scenarios where only one thread can access it at the same time final ConnectPool pool = new ConnectPool(2); for (int i = 0; i < 5; i++) { int id = i + 1; Thread thread = new Thread() { @Override public void run() { try { System.out.println("thread" + id + "Waiting for database connection"); Connect connect = pool.tryAcquireConnect(); System.out.println("thread" + id + "Get database connection:" + connect); //Simulating database operations takes time Thread.sleep(1000); System.out.println("thread" + id + "Release database connections:" + connect); pool.releaseConnect(connect); } catch (InterruptedException e) { } } }; thread.start(); } }

The test results are as follows

Connect1 Start Successfully Connect2 Start Successfully Thread 1 waits to get a database connection Thread 3 waits to get a database connection Thread 2 waits to get a database connection Thread 1 got the database connection:ConCurrent.ClassTest.Semaphore.ConnectPool$Connect@5ac7772e Thread 4 waits to get a database connection Thread 3 got the database connection:ConCurrent.ClassTest.Semaphore.ConnectPool$Connect@10fccd40 Thread 5 waits to get a database connection Thread 1 releases database connections:ConCurrent.ClassTest.Semaphore.ConnectPool$Connect@5ac7772e Thread 2 got the database connection:ConCurrent.ClassTest.Semaphore.ConnectPool$Connect@5ac7772e Thread 3 releases database connections:ConCurrent.ClassTest.Semaphore.ConnectPool$Connect@10fccd40 Thread 4 got the database connection:ConCurrent.ClassTest.Semaphore.ConnectPool$Connect@10fccd40 Thread 2 releases database connections:ConCurrent.ClassTest.Semaphore.ConnectPool$Connect@5ac7772e Thread 5 got the database connection:ConCurrent.ClassTest.Semaphore.ConnectPool$Connect@5ac7772e Thread 4 releases database connections:ConCurrent.ClassTest.Semaphore.ConnectPool$Connect@10fccd40 Thread 5 releases database connections:ConCurrent.ClassTest.Semaphore.ConnectPool$Connect@5ac7772e

That's all about this story

The author is ignorant in his studies. If there are any mistakes in the text, he may wish to correct them.

28 October 2021, 15:25 | Views: 5936

Add new comment

For adding a comment, please log in
or create account

0 comments