Java multithreaded semaphore Semaphore source code analysis

Recently I am engaged in multi-threading. I read a dry article and reprint it.

Original blog: https://www.cnblogs.com/tong-yuan/p/Semaphore.html

 

problem

(1) What is Semaphore?

(2) What are the characteristics of Semaphore?

(3) In what scenarios is Semaphore usually used?

(4) Can the number of licenses of Semaphore be dynamically increased or decreased?

(5) How does Semaphore realize current limiting?

Introduction

Semaphore, semaphore, it stores a series of permits (permits), each call to acquire() will consume a permit, and each call to release() will return a permit.

characteristic

Semaphore is usually used to limit the number of accesses to shared resources at the same time, which is often referred to as current limit.

Let's learn how to implement Semaphore in Java.

Class structure

Semaphore

Semaphore includes a synchronizer Sync that implements AQS, and its two subclasses FairSync and NonFairSync, which shows that Semaphore also distinguishes between fair mode and unfair mode.

Source code analysis

Based on the previous analysis of ReentrantLock and ReentrantReadWriteLock, this article is relatively simple. Some of the methods mentioned before will be skipped directly. Those who are interested can pull to the bottom of the article to view the previous article.

Inner class Sync

// java.util.concurrent.Semaphore.Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;
    // 构造方法,传入许可次数,放入state中
    Sync(int permits) {
        setState(permits);
    }
    // 获取许可次数
    final int getPermits() {
        return getState();
    }
    // 非公平模式尝试获取许可
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            // 看看还有几个许可
            int available = getState();
            // 减去这次需要获取的许可还剩下几个许可
            int remaining = available - acquires;
            // 如果剩余许可小于0了则直接返回
            // 如果剩余许可不小于0,则尝试原子更新state的值,成功了返回剩余许可
            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");
            // 如果原子更新state的值成功,就说明释放许可成功,则返回true
            if (compareAndSetState(current, next))
                return true;
        }
    }
    // 减少许可
    final void reducePermits(int reductions) {
        for (;;) {
            // 看看还有几个许可
            int current = getState();
            // 减去将要减少的许可
            int next = current - reductions;
            // 检测举出
            if (next > current) // underflow
                throw new Error("Permit count underflow");
            // 原子更新state的值,成功了返回true
            if (compareAndSetState(current, next))
                return;
        }
    }
    // 销毁许可
    final int drainPermits() {
        for (;;) {
            // 看看还有几个许可
            int current = getState();
            // 如果为0,直接返回
            // 如果不为0,把state原子更新为0
            if (current == 0 || compareAndSetState(current, 0))
                return current;
        }
    }
}

Through several implementation methods of Sync, we have obtained the following information:

(1) The permission is passed in when the method is constructed;

(2) The permission is stored in the state variable state;

(3) When trying to obtain a license, the value of state is reduced by 1;

(4) When the value of state is 0, no more permission can be obtained;

(5) When a license is released, the value of state is increased by 1;

(6) The number of licenses can be changed dynamically;

Inner class NonfairSync

// java.util.concurrent.Semaphore.NonfairSync
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;
    // 构造方法,调用父类的构造方法
    NonfairSync(int permits) {
        super(permits);
    }
    // 尝试获取许可,调用父类的nonfairTryAcquireShared()方法
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}

In non-fair mode, directly call nonfairTryAcquireShared() of the parent class to try to obtain permission.

Inner class FairSync

// java.util.concurrent.Semaphore.FairSync
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;
            // 没有排队的再尝试更新state的值
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
}

In the fair mode, first check whether there is a queue before it, if there is a queue, it will fail to obtain the permission and enter the queue, otherwise it will try to atomically update the value of state.

Construction method

// 构造方法,创建时要传入许可次数,默认使用非公平模式
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
// 构造方法,需要传入许可次数,及是否公平模式
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

When creating a Semaphore, you need to pass in the number of licenses.

Semaphore is also in unfair mode by default, but you can call the second constructor to declare it as fair mode.

The following methods seem to be relatively simple after studying the previous content. Tong Ge only lists some functions supported by Semaphore.

The following methods are all described for the unfair mode.

acquire() method

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

To obtain a license, the default method is interruptible. If the attempt to obtain a license fails, it will enter the AQS queue.

acquireUninterruptibly()方法

public void acquireUninterruptibly() {
    sync.acquireShared(1);
}

Obtain a license in a non-interrupted manner. If the attempt to obtain a license fails, it will enter the AQS queue.

tryAcquire() method

public boolean tryAcquire() {
    return sync.nonfairTryAcquireShared(1) >= 0;
}

Try to obtain a license, use the unfair mode of Sync to try to obtain a license method, and return regardless of whether the license is obtained, only try once, and will not enter the queue.

tryAcquire(long timeout, TimeUnit unit)方法

public boolean tryAcquire(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

Attempt to obtain a license, first try to obtain a license, if it fails, it will wait for the timeout period. If no license is obtained within this period, then return false, otherwise return true;

release() method

public void release() {
    sync.releaseShared(1);
}

Release a license. When releasing a license, the value of state will increase by 1, and the next thread waiting to obtain the license will be awakened.

acquire(int permits) method

public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireSharedInterruptibly(permits);
}

Obtain multiple licenses at once, and can be interrupted.

acquireUninterruptibly(int permits)方法

public void acquireUninterruptibly(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireShared(permits);
}

Obtain multiple licenses at once, non-disruptively.

tryAcquire(int permits) method

public boolean tryAcquire(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    return sync.nonfairTryAcquireShared(permits) >= 0;
}

Try to obtain multiple licenses at once, and only try once.

tryAcquire(int permits, long timeout, TimeUnit unit)方法

public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
    throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}

Try to obtain multiple licenses and wait for the timeout period. If no license is obtained during this period, it will return false, otherwise it will return true.

release(int permits) method

public void release(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    sync.releaseShared(permits);
}

If multiple permits are released at once, the value of state will increase the number of permits accordingly.

availablePermits()方法

public int availablePermits() {
    return sync.getPermits();
}

Get the number of available licenses.

drainPermits() method

public int drainPermits() {
    return sync.drainPermits();
}

Destroying the number of currently available licenses has no effect on the licenses that have been obtained, and all the remaining licenses will be destroyed.

reducePermits(int reduction)方法

protected void reducePermits(int reduction) {
    if (reduction < 0) throw new IllegalArgumentException();
    sync.reducePermits(reduction);
}

Reduce the number of permits.

to sum up

(1) Semaphore, also called semaphore, is usually used to control access to shared resources at the same time, that is, current limiting scenarios;

(2) The internal implementation of Semaphore is based on the shared lock of AQS;

(3) Semaphore needs to specify the number of permits when it is initialized, and the number of permits is stored in the state;

(4) When a license is obtained, the state value is reduced by 1;

(5) When a license is released, the state value is increased by 1;

(6) N permits can be dynamically reduced;

(7) Can n licenses be dynamically added?

Easter eggs

(1) How to dynamically add n licenses?

Answer: Just call release(int permits). We know that when the license is released, the value of state will increase accordingly. When we look back at the source code of the release license, we find that it is somewhat different from the release lock of ReentrantLock. Semaphore does not check whether the current thread has obtained a license when releasing the license. So you can call the release license method to dynamically add some licenses.

(2) How to achieve current limiting?

Answer: Current limiting, that is, when the traffic suddenly increases, the upper layer must be able to limit the impact of sudden large traffic on downstream services. In a distributed system, current limiting is generally done at the gateway layer. Of course, it can also be used in individual functions. Simply limit the flow, such as a spike scenario. If there are only 10 products that need to be spiked, then the service itself can limit only 100 requests at the same time, and all other requests are invalidated, so the service pressure will not be too great.

Using Semaphore can directly limit the current for this function. The following is the code implementation:

public class SemaphoreTest {
    public static final Semaphore SEMAPHORE = new Semaphore(100);
    public static final AtomicInteger failCount = new AtomicInteger(0);
    public static final AtomicInteger successCount = new AtomicInteger(0);

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(()->seckill()).start();
        }
    }

    public static boolean seckill() {
        if (!SEMAPHORE.tryAcquire()) {
            System.out.println("no permits, count="+failCount.incrementAndGet());
            return false;
        }

        try {
            // 处理业务逻辑
            Thread.sleep(2000);
            System.out.println("seckill success, count="+successCount.incrementAndGet());
        } catch (InterruptedException e) {
            // todo 处理异常
            e.printStackTrace();
        } finally {
            SEMAPHORE.release();
        }
        return true;
    }
}

Guess you like

Origin blog.csdn.net/x950913/article/details/106228905