Semaphore是一个独立的类,它只实现了序列号接口,无继承抽象类或实现业务接口的情况。
使用汽车加油案例,简单介绍Semaphore的使用。油机的台数就可看作是Semaphore的令牌数。一台油机只有在空闲出来时才能给汽车加油。油机状态处于闲置-工作-闲置的状态切换。对应Semaphore的令牌空置-被获取-空置的状态切换。
- public class JucTest {
- private final static int OIL_MACHINE_NUM = 3;
- private final static Semaphore semaphore = new Semaphore(OIL_MACHINE_NUM);
-
- static class Run1 implements Runnable {
- private String name;
-
- private Run1(String name) {
- this.name = name;
- }
- @Override
- public void run() {
- try {
- semaphore.acquire();
- System.out.println(name + " 开始加油");
- Random random = new Random();
- int speedWait = random.nextInt(20) + 10;
- System.out.println(name + "加油耗时" + speedWait);
- Thread.sleep(speedWait * 1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(name + " 加油结束");
- semaphore.release();
- }
- }
-
- public static void main(String[] args) {
- for (int i = 0; i < 9; i++) {
- Thread car = new Thread(new Run1("车辆" + i));
- car.start();
- }
- }
- }
输出结果:
- 车辆0 开始加油
- 车辆1 开始加油
- 车辆2 开始加油
- 车辆0加油耗时27
- 车辆1加油耗时29
- 车辆2加油耗时23
- 车辆2 加油结束
- 车辆3 开始加油
- 车辆3加油耗时18
- 车辆0 加油结束
- 车辆4 开始加油
- 车辆4加油耗时19
- 车辆1 加油结束
- 车辆5 开始加油
- 车辆5加油耗时17
- 车辆3 加油结束
- 车辆6 开始加油
- 车辆6加油耗时16
- 车辆5 加油结束
- 车辆4 加油结束
- 车辆7 开始加油
- 车辆8 开始加油
- 车辆8加油耗时15
- 车辆7加油耗时12
- 车辆6 加油结束
- 车辆7 加油结束
- 车辆8 加油结束
如果当前油机数量只有1个,即令牌只有一个,那么在一个车正在加油时,其他车就在等待。当车加完油,其他车要么排队加油(公平模式),要么抢占加油(非公平)。
内部类Sync
继承AbstractQueuedSynchronizer类,也就是之前文章说的AQS类。
Sync类里提供方法有:
通过构造方法设置令牌数,对应AQS里的state数值。和获取令牌数,返回当前的state数值。
- Sync(int permits) {
- setState(permits);
- }
-
- final int getPermits() {
- return getState();
- }
int nonfairTryAcquireShared(int acquires),用于非公平模式下获取共享锁的状态修改,获取成功,state数值减少,之前的ReentrantLock是获取成功累加,返回state减少后的数值。
boolean tryReleaseShared(int releases),释放共享模式下的锁的状态修改,释放成功,state数值增加。
reducePermits(int reductions),state减少指定数值量,表示令牌个数可一次被使用多个。
int drainPermits(),设置state值等于0 ,表示令牌已被全部取光。
内部类NonfairSync
继承Sync类
通过构造方法接收指定的令牌个数。
tryAcquireShared(int acquires)方法直接跳转到了Sync类的nonfairTryAcquireShared方法。
内部类FairSync
继承Sync类
通过构造方法接收指定的令牌个数。
tryAcquireShared(int acquires)处理流程中会访问AQS的hasQueuedPredecessors方法,查看当前线程是否在队列后方排队。排队就return -1,不排队就通过死循环方式对state值进行修改,并返回修改后的state值。
Semaphore构造方法
默认是非公平的数据获取方式,也可以通过参数来选择使用公平模式
- public Semaphore(int permits) {
- sync = new NonfairSync(permits);
- }
-
- public Semaphore(int permits, boolean fair) {
- sync = fair ? new FairSync(permits) : new NonfairSync(permits);
- }
acquire方法
直接访问AQS类的acquireSharedInterruptibly方法,获取指定个数的令牌。
当前线程如果已中断,AQS会抛出异常。AQS执行子类的tryAcquireShared方法,修改state的数值,当前修改返回结果小于0,说明令牌当前已经被获取完毕,那么当前线程就要被放在等待队列中,并进入到挂起状态。
- public void acquire() throws InterruptedException {
- sync.acquireSharedInterruptibly(1);
- }
-
- public void acquire(int permits) throws InterruptedException {
- if (permits < 0) throw new IllegalArgumentException();
- sync.acquireSharedInterruptibly(permits);
- }
acquireUninterruptibly方法
直接访问AQS类的acquireShared方法,获取指定个数的令牌。
AQS执行子类的tryAcquireShared方法,修改state的数值,当前修改返回结果小于0,说明令牌当前已经被获取完毕,那么当前线程就要被放在等待队列中,并进入到挂起状态。
- public void acquireUninterruptibly() {
- sync.acquireShared(1);
- }
- public void acquireUninterruptibly(int permits) {
- if (permits < 0) throw new IllegalArgumentException();
- sync.acquireShared(permits);
- }
release方法
直接访问AQS类的releaseShared方法。
AQS会先执行Sync类的tryReleaseShared方法,更新state的数值增加。更新成功后执行doReleaseShared方法,做后续线程的唤醒工作。
- public void release() {
- sync.releaseShared(1);
- }
- public void release(int permits) {
- if (permits < 0) throw new IllegalArgumentException();
- sync.releaseShared(permits);
- }
其他方法
availablePermits,获取当前剩余令牌个数
drainPermits,清空令牌数量
hasQueuedThreads,判断是否有等待的线程
getQueueLength,获取当前队列里等待线程的个数
reducePermits,减少指定数量的令牌