文章目录
- 前言
- 概念
- 应用场景
- api接口
- 实战
- 公平锁
- 浅谈源码
- Semaphore重构方法
- acquire方法获取许可
- 打野CAS
- release释放许可
前言
随着18年开始,各个公司的面试难度,也不断在提升,面试也不仅仅停留再ArrayList底层是如何实现的。本文就来介绍一下,我们面试经常会问的一个Semaphore,CountDownLatch、CyclicBarrier都有什么区别。本来 就来说道说道Semaphore。
概念
Semaphore是jdk 1.5以引入的,同版本引入的还有他的两个同胞兄弟,CountDownLatch和CyclicBarrier。位于java.util.concurrent报下,一般简称juc。
一个计数信号量。 在概念上,信号量维持一组许可证,如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。 每个release()添加许可证。
应用场景
- 晚上我们去愿者上钩吃烤鱼,可以看到路外面站着一排排的人,都在做着等叫号,实际上也就是获取许可。
- 茅坑的故事,茅坑是固定的,假设为5个,同一时刻来了10个人,还有5个人就得等待。
- 公司园内停车,实际上车位也是固定的,听不下去,保安就会让你停到其他地方(当然,社长,买不起车,所以,就没有这种烦恼)
api接口
方法 | 描述 |
Semaphore(int permits) | 创建一个 Semaphore与给定数量的许可证和非公平公平设置 |
Semaphore(int permits, boolean fair) | 创建一个 Semaphore与给定数量的许可证和给定的公平设置。 |
acquire() | 从该信号量获取许可证,阻止直到可用 |
acquire(int permits) | 从该信号量获取给定数量的许可证,阻止直到所有可用 |
release() | 释放许可证 |
release(int permits) | 释放一定数量的许可证 |
- 列举一些常用的api接口
实战
模拟一个餐厅叫号,吃饭,结算的一个场景,有4张单人桌子,来了10个客人过来吃饭的一个场景。通过信号量来实现。
package com.cxyxs.thread.fourteen;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Description:
* Author: 程序猿学社
* Date: 2020/3/6 18:41
* Modified By:
*/
public class Demo1 {
public static void main(String[] args) {
//4表示4张桌子
Semaphore semaphore = new Semaphore(4);
for (int i=0;i<10;i++) {
final int index=i;
new Thread(()->{
try {
//1.被服务员叫号 分发许可证
semaphore.acquire();
//2.开始吃饭
System.out.println(Thread.currentThread().getName()+"被服务员叫号后,准备上桌吃饭");
//3.吃饭中 模拟吃饭2s
TimeUnit.SECONDS.sleep(2);
//4.吃完饭,喊服务员结算
semaphore.release();
System.out.println(Thread.currentThread().getName()+"喊服务员结算!");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"客人"+index).start();
}
}
}
运行结果
客人0被服务员叫号后,准备上桌吃饭
客人2被服务员叫号后,准备上桌吃饭
客人1被服务员叫号后,准备上桌吃饭
客人3被服务员叫号后,准备上桌吃饭
客人1喊服务员结算!
客人6被服务员叫号后,准备上桌吃饭
客人3喊服务员结算!
客人5被服务员叫号后,准备上桌吃饭
客人4被服务员叫号后,准备上桌吃饭
客人7被服务员叫号后,准备上桌吃饭
客人2喊服务员结算!
客人0喊服务员结算!
客人4喊服务员结算!
客人5喊服务员结算!
客人9被服务员叫号后,准备上桌吃饭
客人8被服务员叫号后,准备上桌吃饭
客人6喊服务员结算!
客人7喊服务员结算!
客人9喊服务员结算!
客人8喊服务员结算!
- 通过上面的结果,我们可以发现,因为只有4张桌子,每次只能容纳4个人,所以每次同时都有4个人得到许可。
- 客人1结算后,客人6就被叫号咯。
- new Semaphore(4)我们应该理解了把,表示同一时刻,只能有多少个线程同时运行。
- Semaphore内部也是基于AQS实现的,也就是AbstractQueuedSynchronizer的简写。
- 默认采用非公平锁。
- 在实际的业务场景中,限流可能会用到。
公平锁
Semaphore semaphore = new Semaphore(4,true);
- 第二个参数表示,默认为false,表示不公平锁,true为公平锁。
公平锁,既先进先出,可以理解为消息队列,先生存的消息,在队列的头部。
不公平锁,既可能存在插队现象。
浅谈源码
Semaphore重构方法
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
- 发现有FairSync和NonfairSync两个类,既公平锁和非公平锁。如果为true,则为公平锁。
private volatile int state;
/**
* Sets the value of synchronization state.
* This operation has memory semantics of a {@code volatile} write.
* @param newState the new state value
*/
protected final void setState(int newState) {
state = newState;
}
- 实际上,setState这个方法,不要被这个方法名,混淆了我们的视觉,实际上,这个方法就是调用父类AQS的setState,设置计数。
acquire方法获取许可
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
- 不能这是为负数,负数会抛出IllegalArgumentException异常
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
- 减去获取的许可,判断会不会小于0
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
- for (; ) 是不是看起来也很少见,等同于while(true),实际上在源码里面几乎都是使用的这种方式。指令少,不占用寄存器,而且没有判断跳转
- 判断原有的许可的计数减去需要获取的是否为空。
- 这又是我们的老朋友CAS(compareAndSetState的简称)
打野CAS
简单的理解CAS是怎么一回事。并且,他经常露脸压线,不抓你,抓谁。通过一个简单的小例子,理解一下CAS
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(1);
atomicInteger.compareAndSet(1,2);
System.out.println(atomicInteger.get());
}
- 他的结果是2,why?
- 实际上他的初始化为1,调用compareAndSet方法,会判断跟原来的值是否相等,如果相等,则改为2。是不是还有有点小懵逼。别急。我们来看看他的源码。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
- expect 期望的值,update需要修改的值。
- 如果当前的值和我期望的值相等,则改为update参数对应的值。
release释放许可
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
- 调用父类AQS的releaseShared方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- 调用Sync.tryReleaseShared方法
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
- 跟获取信号量的源码类似,只是一个是减去releases,释放则为增加releases操作。