Semaphore 多线程并发控制
01 Semaphore
Semaphore 的作用就是控制某段程序线程并发执行的数量。这比 sychronized 功能更加强大和方便。
Semaphore 有个一个参数为 permits 的构造函数。(permist 指的是同一时间内允许多少个线程同时执行 acquire()和 release()之间的代码。
...
private final Semaphore semaphore = new Semaphore(1);
...
public void work() {
...
semaphore.acquire();
...
semaphore.relose();
...
}
Semaphore 只能控制共用一个 Semaphore 对象的线程时间的同步。 不同 semaphore 之间的线程时无法控制的。
02 Semaphore 中的常用方法
构造函数参数 permits,通过构造参数指定许可证数量。
acquire()
: 使用构造函数中的 permits 中的值,限制线程执行 acquire和release 之间的线程数。
acquire(int permits)
每执行一次从 初始化的 permits 申请 x 个认证数,申请了 x 个必须 release x 个证书才能继续向下执行。
acquireUninterruptibly()
使进入 acquire()方法线程,不允许被中断。
availablePermits()
返回当前可用的许可数。
drainPermits()
返回当前可用的许可数,并将许可数设置为 0
getQueueLength()
取得等待许可的线程数
hasQueueLength()
判断有没有线程在等待这个许可。
tryAcquire()
尝试获取一个许可,若获取不到则返回 false,此方法通常与 if 结合,具有无阻塞的特点(即不需要在等待处一直等待),若 if 不成立则直接返回 else 语句。
tryAcquire(int permits)
尝试获取 permits 个许可,若获取不到则返回 false
tryAcquire(long timeout, TimeUnit unit)
在制定的实现内获取 1 个许可
tryAcquire(int permits, long timeout, TimeUnit unit)
尝试在指定的时间内获取 permits 个许可。
03 公平和非公平信号量
公平信号量:获得锁的顺序与线程执行的顺序有关。
非公平信号量:获得锁的顺序与线程执行的顺序无关。
04 扩展
(1)线程和 CPU 之间的关系。
线程过多会导致 CPU 资源被耗尽,每个线程执行过程都相当缓慢,因为 CPU 除了把时间片段分配给了不同的线程,在切换不同上下文时也是需要时间的,因此线程数过多会导致系统性能大幅降低。
(2)多线程同步
多线程同步指的是排队执行某一任务,执行的任务是一个个去执行的,并不能并行。
(3)缺点
permits 增加了控制的难度。
例如,创建 Semaphore 是指定的 permits 的数量为 1, 但是执行 semaphore.acquire(2) 时指定要申请的 permits 数量为 2,总共的许可数有 1 个,但是申请 2 个,那么程序就会 block 住,因为当许可不够时,就会一直等待,使用 semephore 对象的任何线程都会阻塞。
private Semaphore semaphore = new Semaphore(1);
private void method() {
semaphore.acquire(2); // 由于只有一个 1 个证书可申请,因此会一直阻塞。
...
semaphore.release(2);
}
同样会造成 block 的情况还有一种情况,当执行 semaphore.acquire(3) 申请了两个许可证,但是后续 只执行了一次 semaphore.release() 操作或者执行的 release 许可证的的数量小于3,那么同样会造成 block。
private Semaphore semaphore = new Semaphore(2);
...
public void method() {
semaphore.acquire(2);
...
semaphore.release(); // 只 release 了一个证书,还需在执行一次 release 或者 release 执行证书数为 2
}
(4)permits 指定数量带来的扩展
Semaphore 可以有效的对并发执的数量进行控制,因此可以应用在多个场景下。
(1)同步操作。同一时间只能由一个线程进行操作。
(2)动态调整性能。根据 CPU 的负载情况,动态调整当前任务并行处理的线程数。