Java concurrent packet count semaphore Semaphore

foreword

Previously, we learned about AQS and the Du occupying lock ReentrantLock based on AQS and the synchronization aid CyclicBarrier based on ReentrantLock. In this section, we learn another class Semaphore provided by JDK. Semaphore is translated as "semaphore", and the Semaphore provided by JDK is called a counting semaphore . According to the Java Doc, Semaphore maintains a set of permissions, or some resources, and can then limit the number of threads that can access this set of permissions (also called resources) at the same time.

 

In essence, we can actually understand Semaphore as a "shared lock", and when there is only one permission set or shared resource maintained by Semaphore, it changes from "shared lock" to ReentrantLock's "Du account" Lock", the semaphore at this time is also called "mutual exclusion semaphore" Mutex. Remember the example we gave when we customized the instance of shared synchronization components in the continued chapter of AQS, one of the core frameworks for concurrent packets in Java ? In the scenario where ten people go to a bank with only 3 counters, Semaphore is a shared lock that can be used to solve this scenario. Of course, the usefulness of Semaphore is not limited to this. We can also access certain resources according to Semaphore restrictions. The characteristics of the number of threads can meet the different needs of many different scenarios.

 

Example of use

First of all, let's start with using examples to understand Semaphore. After all, it is necessary to have an intuitive preliminary understanding of Semaphore before going deeper into it. Let's take the previous "scenario of ten people going to a bank with only 3 counters to handle business" as an example to illustrate the usage of Semaphore.

public static void main(String[] args) {
	Semaphore semaphore = new Semaphore (3);
	for (int i = 0; i < 10 ; i++) {  
		new handleThread(semaphore, "线程"+i).start();  
	}  
}

static class handleThread extends Thread{  
	
	private Semaphore semaphore;  
	  
	public handleThread(Semaphore semaphore, String name) {  
		super();  
		this.semaphore = semaphore;  
		setName(name);  
	}  
	  
	@Override  
	public void run() {  
		System.out.println(Thread.currentThread().getName() + "Start waiting");  
		try {
			semaphore.acquire(); //Note that the failure to acquire shared resources here should not perform the resource release process.
		} catch (InterruptedException e1) {
			return;
		}
		try {  
			System.out.println(Thread.currentThread().getName() + "Start processing");  
			Thread.sleep(5000);  
			System.out.println(Thread.currentThread().getName() + "end of process");  
		} catch(Exception e){  
			e.printStackTrace ();  
		}finally{  
			semaphore.release();  
		}  
	}  
}

    Pass in 3 through the Semaphore construction method, set the number of shared resources in this group to 3, and then create 10 threads, each thread applies for a window resource through semaphore.acquire(), and completes semaphore.release() to release the window resource. It can be seen that the use of Semaphore is very convenient to allow ten threads to share access to a group (3) of shared resources. It is worth noting that the semaphore.acquire() method will throw an interrupt exception. Remember not to release shared resources when it throws an exception, otherwise an error will occur.

 

Semaphore source code analysis

Through a simple understanding, we know that Semaphore is actually an implementation of shared locks. Next, let's take a look at its source code implementation. First we start with the structure of its class:

It can be found from the class structure that its implementation is almost the same as that of ReentrantLock. It also delegates the specific implementation of the synchronizer to the abstract static inner class Sync. On this basis, fair and unfair modes are implemented respectively. The only difference is The fact is that Semaphore does not implement the Lock interface. So Semaphore can not be called a synchronization lock. Next, let's look at the list of methods provided by Semaphore in addition to the constructor,

 These are the main methods of Semaphore, and there are several methods for returning the status of the waiting queue, which are very simple and will not be listed. As can be seen from the listed methods, it mainly focuses on the implementation of blocking and non-blocking permissions. Below we analyze the main method source code.

 

Construction method

public Semaphore(int permits) {
	sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
	sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

    It can be seen from the construction method that Semaphore also uses the unfair mode by default, because the throughput of unfair sorting is usually higher than that of the fair mode.

 

Unfair licensing

final int nonfairTryAcquireShared(int acquires) {
	for (;;) {
		int available = getState();//当前可用许可
		int remaining = available - acquires;
                //如果剩下的许可数满足申请的许可数,直接尝试通过CAS获取许可
		if (remaining < 0 || compareAndSetState(available, remaining))
			return remaining;
	}
}

    在Semaphore类的中那四个tryAcquire命名的非阻塞式方法以及非公平模式下的四个阻塞式acquire*方法最终在尝试获取共享资源的时候都是执行的nonfairTryAcquireShared()方法,它的逻辑很简单,如果当前有足够的许可剩余,直接通过CAS获取许可,成功就返回,失败才有走AQS的入队逻辑,阻塞起来等待唤醒。

 

公平式获取许可

protected int tryAcquireShared(int acquires) {
	for (;;) {
                //hasQueuedPredecessors方法很简单,表示查看是否有非当前线程的其他线程正在等待获取共享资源
                //如果队列为空,或者当前线程是头节点的后继节点就返回false
		if (hasQueuedPredecessors())
			return -1;
		int available = getState();
		int remaining = available - acquires;
		if (remaining < 0 ||
			compareAndSetState(available, remaining))
			return remaining;
	}
}

    不同于非公平式获取许可,公平式获取许可的逻辑和ReentrantLock中获取公平锁类似,在尝试获取共享资源之前需要先通过hasQueuedPredecessors()方法判断当前线程是否是FIFO队列中最有资格获取共享资源的线程,否则进入FIFO队列排队。

 

drainPermits方法的含义

对于查看许可数量的两个方法,分别是availablePermits()方法和drainPermits()方法,单凭注释可能很难区别它们的含义,特别是drainPermits(),而availablePermits()很好理解,它其实就是返回当前还剩余的许可数量,直接调用的是AQS的getState()方法,返回当前state变量的值,而drainPermits()是什么意思呢?它最终调用的是Semaphore的抽象内部类Sync的drainPermits()方法:

final int drainPermits() {
	for (;;) {
		int current = getState();
		if (current == 0 || compareAndSetState(current, 0))
			return current;
	}
}

    通过源码我们发现,当剩余许可数为0时直接返回0;当许可数不为0时,使用CAS操作尝试直接将当前许可数设置为0,直到成功才返回原始剩余的许可数。可见,drainPermits()方法其实就是在还剩有许可的时,立即将剩余许可清0,并返回清0之前还剩余的实际许可数。

 

内存可见性

Semaphore其实就是对共享锁的一种实现,只是他没有实现Lock接口,所以不能直接称之为“锁”,但是它依然满足happens-before中的锁定规则,即“一个unlock操作先行发生于后面对同一个锁的lock操作”。所以,某个线程对release方法的调用happens-before紧接着的另一个线程对aquire方法的成功调用。也就是说,在某个线程执行release方法之前对共享变量的修改,对另一个紧跟着的线程成功执行aquire方法后是立即可见的。

 

Semaphore其他应用场景

用Semaphore来实现一个有界容量的List:

public class BoundedList<T> {  
	  
    private final List<T> list;  
    private final Semaphore semaphore;  
  
    public BoundedList(int bound) {  
        list = Collections.synchronizedList(new LinkedList<T>());  
        semaphore = new Semaphore(bound);  
    }  
  
    public boolean add(T obj) throws InterruptedException {  
        semaphore.acquire();  
        boolean addedFlag = false;  
        try {  
            addedFlag = list.add(obj);  
        } finally {  
            if (!addedFlag) {  
                semaphore.release();  
            }  
        }  
        return addedFlag;  
    }  
  
    public boolean remove(Object obj) {  
        boolean removedFlag = list.remove(obj);  
        if (removedFlag) {  
            semaphore.release();  
        }  
        return removedFlag;  
    }  
      
    // 其他操作委托给底层的List,这里只列举出一个方法  
    public T get(int index) {  
        return list.get(index);  
    }  
      
    // 其他方法……  
  
}

在使用Semaphore时,切记不能再semaphore.acquire()方法抛出异常之后释放资源,所以这里 add(T obj)方法直接将中断异常抛出了方法本身。

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326108892&siteId=291194637