信号量 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); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?