Semaphore 是一个计数信号量,必须由获取它的线程释放
。Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。就这一点而言,单纯的synchronized 关键字是实现不了的。常用于限制可以访问某些资源的线程数量,例如通过 Semaphore 限流。
Semaphore 只有3个操作:
acquire(int permits)
:表示每调用1次此方法,就使用
Semaphore中的x(方法中的参数)个permits。release(int permits)
:表示每调用1次此方法,就释放
Semaphore中的x(方法中的参数)个permits。package com.lcao.aqs;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @author
* @title: SemaphoreExample
* @description: Semaphore 信号量 例子
* @date 2020/4/20 11:03
*/
@Slf4j
public class SemaphoreExample {
private static Integer threadCount = 20;
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
// 实例化时定义信号量许可数,这里允许20
final Semaphore semaphore = new Semaphore(20);
for (int i=0; i<threadCount;i++) {
final int num = i;
threadPool.execute(()->{
try {
// 获取5个许可,5表示进入此代码,就会消耗5个通路(许可),5个通路(许可)从20个中扣除
semaphore.acquire(5);
testMethod(num);
semaphore.release(5); // 释放占用的 5 个通路(许可)
} catch (Exception e) {
log.info("多线程调用失败!");
}
});
}
threadPool.shutdown();
}
private static void testMethod(int num) throws Exception{
Thread.sleep(1000); // 这里是为了让控制台打印更直观
log.info("{}-num = {}",Thread.currentThread().getId(),num);
}
}
运行结果:
09:29:53.736 [pool-1-thread-4] INFO com.lcao.aqs.SemaphoreExample - 14-num = 3
09:29:53.736 [pool-1-thread-1] INFO com.lcao.aqs.SemaphoreExample - 11-num = 0
09:29:53.736 [pool-1-thread-3] INFO com.lcao.aqs.SemaphoreExample - 13-num = 2
09:29:53.736 [pool-1-thread-2] INFO com.lcao.aqs.SemaphoreExample - 12-num = 1
09:29:54.743 [pool-1-thread-6] INFO com.lcao.aqs.SemaphoreExample - 16-num = 5
09:29:54.743 [pool-1-thread-5] INFO com.lcao.aqs.SemaphoreExample - 15-num = 4
09:29:54.743 [pool-1-thread-7] INFO com.lcao.aqs.SemaphoreExample - 17-num = 6
09:29:54.743 [pool-1-thread-8] INFO com.lcao.aqs.SemaphoreExample - 18-num = 7
09:29:55.743 [pool-1-thread-9] INFO com.lcao.aqs.SemaphoreExample - 19-num = 8
09:29:55.743 [pool-1-thread-11] INFO com.lcao.aqs.SemaphoreExample - 21-num = 10
09:29:55.743 [pool-1-thread-10] INFO com.lcao.aqs.SemaphoreExample - 20-num = 9
09:29:55.743 [pool-1-thread-12] INFO com.lcao.aqs.SemaphoreExample - 22-num = 11
09:29:56.744 [pool-1-thread-14] INFO com.lcao.aqs.SemaphoreExample - 24-num = 13
09:29:56.744 [pool-1-thread-15] INFO com.lcao.aqs.SemaphoreExample - 25-num = 14
09:29:56.744 [pool-1-thread-13] INFO com.lcao.aqs.SemaphoreExample - 23-num = 12
09:29:56.744 [pool-1-thread-16] INFO com.lcao.aqs.SemaphoreExample - 26-num = 15
09:29:57.744 [pool-1-thread-20] INFO com.lcao.aqs.SemaphoreExample - 30-num = 19
09:29:57.744 [pool-1-thread-18] INFO com.lcao.aqs.SemaphoreExample - 28-num = 17
09:29:57.744 [pool-1-thread-19] INFO com.lcao.aqs.SemaphoreExample - 29-num = 18
09:29:57.744 [pool-1-thread-17] INFO com.lcao.aqs.SemaphoreExample - 27-num = 16
Process finished with exit code 0
分析:
semaphore.acquire(5); 表示进入这行代码,就有5个通道(许可)被消耗。上述代码声明Semaphore时一共有20个通道(许可)。因为上述代码testMethod(int num)方法做了 Thread.sleep(1000)操作,所以控制台,每秒打印 20/5=4条日志记录。
查看 Semaphore 的部分源码,分析 acquire() 和 release() 有参方法与无参方法的区别。
package java.util.concurrent;
import java.util.Collection;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class Semaphore implements java.io.Serializable {
private static final long serialVersionUID = -3222578661600680210L;
/** All mechanics via AbstractQueuedSynchronizer subclass */
private final Sync sync;
// 省略代码 ……
/**
* Creates a {@code Semaphore} with the given number of
* permits and nonfair fairness setting.
*
* @param permits the initial number of permits available.
* This value may be negative, in which case releases
* must occur before any acquires will be granted.
*/
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
// 这里是 acquire() 无参方法
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 这里是 acquire() 有参方法
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
// 这里是 release() 无参方法
public void release() {
sync.releaseShared(1);
}
// 这里是 release() 有参方法
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
// 省略代码 ……
}
很明显 acquire() 和 release() 的无参方法,都是给了一个默认的许可数 1,等同于其各自的有参方法,只是有参方法的参数都是1.
示例:
package com.lcao.aqs;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @author
* @title: SemaphoreExample
* @description: Semaphore 信号量 例子
* @date 2020/4/20 11:03
*/
@Slf4j
public class SemaphoreExample {
private static Integer threadCount = 20;
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
// 实例化时定义信号量许可数,这里允许3
final Semaphore semaphore = new Semaphore(3);
for (int i=0; i<threadCount;i++) {
final int num = i;
threadPool.execute(()->{
try {
semaphore.acquire(); // 默认获取1个许可
testMethod(num);
semaphore.release(); // 默认释放1个许可
} catch (Exception e) {
log.info("多线程调用失败!");
}
});
}
threadPool.shutdown();
}
private static void testMethod(int num) throws Exception{
Thread.sleep(1000);
log.info("{}-num = {}",Thread.currentThread().getId(),num);
}
}
运行结果:
11:51:14.883 [pool-1-thread-3] INFO com.lcao.aqs.SemaphoreExample - 13-num = 2
11:51:14.883 [pool-1-thread-2] INFO com.lcao.aqs.SemaphoreExample - 12-num = 1
11:51:14.883 [pool-1-thread-1] INFO com.lcao.aqs.SemaphoreExample - 11-num = 0
11:51:15.888 [pool-1-thread-5] INFO com.lcao.aqs.SemaphoreExample - 15-num = 4
11:51:15.888 [pool-1-thread-4] INFO com.lcao.aqs.SemaphoreExample - 14-num = 3
11:51:15.888 [pool-1-thread-6] INFO com.lcao.aqs.SemaphoreExample - 16-num = 5
11:51:16.889 [pool-1-thread-7] INFO com.lcao.aqs.SemaphoreExample - 17-num = 6
11:51:16.889 [pool-1-thread-8] INFO com.lcao.aqs.SemaphoreExample - 18-num = 7
11:51:16.889 [pool-1-thread-9] INFO com.lcao.aqs.SemaphoreExample - 19-num = 8
11:51:17.889 [pool-1-thread-11] INFO com.lcao.aqs.SemaphoreExample - 21-num = 10
11:51:17.889 [pool-1-thread-10] INFO com.lcao.aqs.SemaphoreExample - 20-num = 9
11:51:17.890 [pool-1-thread-12] INFO com.lcao.aqs.SemaphoreExample - 22-num = 11
11:51:18.890 [pool-1-thread-13] INFO com.lcao.aqs.SemaphoreExample - 23-num = 12
11:51:18.890 [pool-1-thread-14] INFO com.lcao.aqs.SemaphoreExample - 24-num = 13
11:51:18.891 [pool-1-thread-15] INFO com.lcao.aqs.SemaphoreExample - 25-num = 14
11:51:19.890 [pool-1-thread-16] INFO com.lcao.aqs.SemaphoreExample - 26-num = 15
11:51:19.890 [pool-1-thread-17] INFO com.lcao.aqs.SemaphoreExample - 27-num = 16
11:51:19.891 [pool-1-thread-18] INFO com.lcao.aqs.SemaphoreExample - 28-num = 17
11:51:20.891 [pool-1-thread-19] INFO com.lcao.aqs.SemaphoreExample - 29-num = 18
11:51:20.891 [pool-1-thread-20] INFO com.lcao.aqs.SemaphoreExample - 30-num = 19
Process finished with exit code 0
这里由于 实例化时定义信号量许可数,这里允许3,代码:final Semaphore semaphore = new Semaphore(3)
,所以每秒只有3条日志信息打印。
tryAcquire()
: 尝试获取一个许可,成功返回TRUE失败返回false 。tryAcquire(int permits)
: 尝试获取 permits 个许可,成功返回TRUE失败返回false 。tryAcquire(long timeout, TimeUnit unit)
:在一定时间内尝试获取一个许可。第一个参数是等待的时间,第二个是等待时间的单位。tryAcquire(int permits, long timeout, TimeUnit unit)
:在一定时间内尝试获取 permits 个许可。第一个参数是等待的时间,第二个是等待时间的单位。示例:
package com.lcao.aqs;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @author
* @title: SemaphoreExample
* @description: Semaphore 信号量 例子
* @date 2020/4/20 11:03
*/
@Slf4j
public class SemaphoreExample {
private static Integer threadCount = 20;
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
// 实例化时定义信号量许可数,这里允许3
final Semaphore semaphore = new Semaphore(6);
for (int i=0; i<threadCount;i++) {
final int num = i;
threadPool.execute(()->{
try {
if(semaphore.tryAcquire(2)) { // 尝试获取2个许可
testMethod(num);
semaphore.release(2); // 释放2个许可
}
} catch (Exception e) {
log.info("多线程调用失败!");
}
});
}
threadPool.shutdown();
}
private static void testMethod(int num) throws Exception{
Thread.sleep(1000);
log.info("{}-num = {}",Thread.currentThread().getId(),num);
}
}
执行结果:
14:08:07.959 [pool-1-thread-2] INFO com.lcao.aqs.SemaphoreExample - 12-num = 1
14:08:07.959 [pool-1-thread-1] INFO com.lcao.aqs.SemaphoreExample - 11-num = 0
14:08:07.959 [pool-1-thread-3] INFO com.lcao.aqs.SemaphoreExample - 13-num = 2
Process finished with exit code 0
.