信号量 Semaphore

Semaphore

Semaphore 更加适合用于控制对有限资源的访问,特别是当你需要允许一定数量的线程同时访问资源时

CountDownLatch 更加适合用于协调多个线程的完成状态,确保在某些操作完成后再执行后续操作

它用于控制对共享资源的访问,通过维护一个许可的计数器来限制同时访问某个资源的线程数量,常用方法如下

Semaphore semaphore = new Semaphore(3); // 允许最多3个线程同时访问资源
semaphore.acquire(); // 线程在访问共享资源之前需要调用 acquire() 方法来获取许可。如果没有可用的许可,线程将会被阻塞,直到有许可可用。
semaphore.release(); // 线程在完成对共享资源的访问后,需要调用 release() 方法来释放许可。释放许可后,其他被阻塞的线程可以获得许可并继续执行
semaphore.tryAcquire(); // 用于尝试获取许可但不会阻塞。如果无法立即获取许可,该方法将返回 false
semaphore.acquire(2); // 获取2个许可
semaphore.release(2); // 释放2个许可

用法示例

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private static final int MAX_PERMITS = 3; // 许可总数
    private static final Semaphore semaphore = new Semaphore(MAX_PERMITS); // 创建一个信号量(持有3个许可)

    public static void main(String[] args) {
        Runnable task = () -> {
            try {
                System.out.println(Thread.currentThread().getName() + " 尝试获取许可");
                semaphore.acquire(); // 获取许可(每获取一个池子中的许可就少一个)
                System.out.println(Thread.currentThread().getName() + " 得到许可");

                Thread.sleep(2000);

                System.out.println(Thread.currentThread().getName() + " 释放许可");
                semaphore.release(); // 释放许可(释放的许可会回到信号量池子中)
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        };

        for (int i = 0; i < 6; i++) {
            new Thread(task).start();
        }
    }
}

原理

  • 创建 Semaphore 实例

    同 ReentrantLcok,默认创建非公平锁,也可以带参数指定公平还是非公平,正数的参数会设置给 AQS 的 state 变量

    // 构造方法
    public Semaphore(int permits) {
        // 创建了一个非公平锁
        sync = new NonfairSync(permits);
    }
    
    // 非公平锁,继承 Sync,和前面的锁一样,这个 Sync 也是继承 AQS 
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;
    
        NonfairSync(int permits) {
            // 调用父类构造方法,也就是 Sync
            super(permits);
        }
    
        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }
    
    // Sync 构造方法
    Sync(int permits) {
        // 这个方法是 AQS 的
        setState(permits);
    }
    
    // java.util.concurrent.locks.AbstractQueuedSynchronizer#setState
    protected final void setState(int newState) {
        // 设置 state 变量
        state = newState;
    }
    
  • 获取许可

    // java.util.concurrent.Semaphore#acquire()
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
    // java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 获取许可,模板方法,当小于 0,就说明当前没有许可了,返回值大于0,什么都不做,表示获取锁成功(内部已经 cas 维护了 state)
        if (tryAcquireShared(arg) < 0) 
            doAcquireSharedInterruptibly(arg); // 如果许可不足,走这里
    }
    
    // java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquireShared
    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
    
    // java.util.concurrent.Semaphore.NonfairSync#tryAcquireShared
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
    
    // java.util.concurrent.Semaphore.Sync#nonfairTryAcquireShared
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            int available = getState(); // 拿到 state(如果是 2 表示已经颁发了 2 个许可了)
            int remaining = available - acquires; // 计算剩余许可,可能小于 0,表示没有许可了,可能大于 0,表示许可够
            if (remaining < 0 ||
                compareAndSetState(available, remaining)) // 如果 remaining 不小于 0,说明许可是够的,cas 设置 state(当前剩余许可设置到 state)
                return remaining; // 返回 remaining
        }
    }
    
    // java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly
    private void doAcquireSharedInterruptibly(int arg) // 这个现在太熟悉了,看过太多次了,线程入队,维护节点,挂起线程
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
posted @   CyrusHuang  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示