1.Semaphore
信号量,用来限制能同时访问共享资源的线程上限。
1.1 基本使用
/**
* @ClassName SemaphoreTest
* @author: shouanzh
* @Description 信号量,用来限制能同时访问共享资源的线程上限。
* @date 2022/3/20 11:32
*/
@Slf4j
public class SemaphoreTest {
public static void main(String[] args) {
// 1. 创建 Semaphore 对象
Semaphore semaphore = new Semaphore(3); // 限制上限为3
// 2. 10个线程同时运行
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
// 3.获得此信号量
semaphore.acquire();
log.debug("获得信号量");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 4.释放信号量
semaphore.release();
}
},"t" + i).start();
}
}
}
运行结果
// 每次运行三个线程
2022-03-20 11:45:05 [t0] - 获得信号量
2022-03-20 11:45:05 [t2] - 获得信号量
2022-03-20 11:45:05 [t1] - 获得信号量
2022-03-20 11:45:07 [t3] - 获得信号量
2022-03-20 11:45:07 [t4] - 获得信号量
2022-03-20 11:45:07 [t5] - 获得信号量
2022-03-20 11:45:09 [t6] - 获得信号量
2022-03-20 11:45:09 [t7] - 获得信号量
2022-03-20 11:45:09 [t8] - 获得信号量
2022-03-20 11:45:11 [t9] - 获得信号量
Process finished with exit code 0
1.2 Semaphore原理-图解流程
加锁解锁流程
Semaphore 有点像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就像 是获得了停车位,然后停车场显示空余车位减一
刚开始,permits (state)为3,这时5个线程来获取资源
假设其中 Thread-1, Thread-2, Thread-4 CAS竞争成功,而 Thread-0 和 Thread-3竞争失 败,进入AQS 队列park阻塞
这时 Thread-4释放了 permits,状态如下
接下来 Thread-0竞争成功,permits 再次设置为0,设置自己为 head节点,断开原来的 head 节点, unpark接下来的 Thread-3 节点,但由于 permits 是0,因此 Thread-3 在尝试 不成功后再次进入 park状态
1.3 Semaphore原理-源码分析
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
// permits 即 state
super(permits);
}
// Semaphore 方法, 方便阅读, 放在此处
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// AQS 继承过来的方法, 方便阅读, 放在此处
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
// 尝试获得共享锁
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
// Sync 继承过来的方法, 方便阅读, 放在此处
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (
// 如果许可已经用完, 返回负数, 表示获取失败, 进入 doAcquireSharedInterruptibly
remaining < 0 ||
// 如果 cas 重试成功, 返回正数, 表示获取成功
compareAndSetState(available, remaining)
) {
return remaining;
}
}
}
// AQS 继承过来的方法, 方便阅读, 放在此处
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) {
// 成功后本线程出队(AQS), 所在 Node设置为 head
// 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark
// 如果 head.waitStatus == 0 ==> Node.PROPAGATE
// r 表示可用资源数, 为 0 则不会继续传播
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 不成功, 设置上一个节点 waitStatus = Node.SIGNAL, 下轮进入 park 阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// Semaphore 方法, 方便阅读, 放在此处
public void release() {
sync.releaseShared(1);
}
// AQS 继承过来的方法, 方便阅读, 放在此处
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
// Sync 继承过来的方法, 方便阅读, 放在此处
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;
}
}
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒后续节点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
}
2.CountdownLatch
用来进行线程同步协作,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。
其中构造参数用来初始化等待计数值,await()用来等待计数归零,countDown()用来让计数减一
CountDownLatch 是共享锁的一种实现,它默认构造 AQS 的 state 值为 count。当线程使用 countDown方法时,其实使用了 tryReleaseShared 方法以CAS 的操作来减少 state ,直至 state 为 0 就代表所有的线程都调用了countDown方法。当调用 await 方法的时候,如果 state 不为0,就代表仍然有线程没有调用 countDown 方法,那么就把已经调用过 await 的线程都放入阻塞队列 Park ,并自旋 CAS 判断 state == 0,直至最后一个线程调用了 countDown ,使得 state == 0,于是阻塞的线程便判断成功,全部往下执行。
代码举例
/**
* CountDownLatchTest 用来进行线程同步协作,它允许一个或多个线程一直等待,
* 直到其他线程的操作执行完毕再执行。
*/
@Slf4j
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
method3();
}
/**
* 基本使用
* @throws InterruptedException
*/
public static void method1() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(() -> {
log.debug("begin...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
log.debug("end...");
},"t1").start();
new Thread(() -> {
log.debug("begin...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
log.debug("end...");
},"t2").start();
new Thread(() -> {
log.debug("begin...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
log.debug("end...");
},"t3").start();
log.debug("await...");
countDownLatch.await();
log.debug("await end...");
/**
* 2022-03-20 13:07:14 [t3] - begin...
* 2022-03-20 13:07:14 [main] - await...
* 2022-03-20 13:07:14 [t1] - begin...
* 2022-03-20 13:07:14 [t2] - begin...
* 2022-03-20 13:07:15 [t1] - end...
* 2022-03-20 13:07:16 [t2] - end...
* 2022-03-20 13:07:17 [t3] - end...
* 2022-03-20 13:07:17 [main] - await end...
*
* Process finished with exit code 0
*/
}
/**
* 改进
*/
public static void method2() {
CountDownLatch countDownLatch = new CountDownLatch(3);
ExecutorService executorService = Executors.newFixedThreadPool(4);
executorService.submit(() -> {
log.info("t1 start ...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
log.info("t1 end ...{}", countDownLatch.getCount());
});
executorService.submit(() -> {
log.info("t2 start ...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
log.info("t2 end ...{}", countDownLatch.getCount());
});
executorService.submit(() -> {
log.info("t3 start ...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
log.info("t3 end ...{}", countDownLatch.getCount());
});
// 等待其它线程执行完成
executorService.submit(() -> {
log.info("main wait ...");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("main wait end ...");
executorService.shutdown();
});
/**
* 2022-03-20 13:50:20 [pool-1-thread-2] - t2 start ...
* 2022-03-20 13:50:20 [pool-1-thread-3] - t3 start ...
* 2022-03-20 13:50:20 [pool-1-thread-1] - t1 start ...
* 2022-03-20 13:50:20 [pool-1-thread-4] - main wait ...
* 2022-03-20 13:50:21 [pool-1-thread-1] - t1 end ...2
* 2022-03-20 13:50:22 [pool-1-thread-2] - t2 end ...1
* 2022-03-20 13:50:23 [pool-1-thread-3] - t3 end ...0
* 2022-03-20 13:50:23 [pool-1-thread-4] - main wait end ...
*
* Process finished with exit code 0
*/
}
/**
* 用来进行线程同步协作,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。
* @throws InterruptedException
*/
public static void method3() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
ExecutorService executorService = Executors.newFixedThreadPool(10);
String[] all = new String[10];
Random random = new Random();
for(int i = 0; i < 10; i++) {
int id = i;
executorService.submit(() -> {
for (int j = 0; j <= 100; j++) {
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
all[id] = j + "%";
System.out.print("\r" + Arrays.toString(all));
}
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println();
log.debug("游戏开始...");
executorService.shutdown();
/**
* [100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%]
* 2022-03-20 14:01:34 [main] - 游戏开始...
*
* Process finished with exit code 0
*/
}
}
3.CyclicBarrier
循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行。跟 CountdownLatch 一样,但这个可以重用。
/**
* @ClassName CyclicBarrierTest
* @author: shouanzh
* @Description 循环栅栏,用来进行线程协作,等待线程满足某个计数。
* 构造时设置『计数个数』,每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待,
* 当等待的线程数满足『计数个数』时,继续执行。跟 CountdownLatch 一样,但这个可以重用。
* @date 2022/3/20 14:38
*/
@Slf4j
public class CyclicBarrierTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
log.info("task1,task2 finish ...");
});
for(int i = 0; i < 3; i++) {
executorService.submit(() -> {
log.info("task1 begin ...");
try {
Thread.sleep(1000);
cyclicBarrier.await(); // 2-1 = 1
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
executorService.submit(() -> {
log.info("task2 begin ...");
try {
Thread.sleep(2000);
cyclicBarrier.await(); // 1-1 = 0
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
运行结果
2022-03-20 14:52:06 [pool-1-thread-2] - task2 begin ...
2022-03-20 14:52:06 [pool-1-thread-1] - task1 begin ...
2022-03-20 14:52:08 [pool-1-thread-2] - task1,task2 finish ...
2022-03-20 14:52:08 [pool-1-thread-2] - task1 begin ...
2022-03-20 14:52:08 [pool-1-thread-1] - task2 begin ...
2022-03-20 14:52:10 [pool-1-thread-1] - task1,task2 finish ...
2022-03-20 14:52:10 [pool-1-thread-1] - task1 begin ...
2022-03-20 14:52:10 [pool-1-thread-2] - task2 begin ...
2022-03-20 14:52:12 [pool-1-thread-2] - task1,task2 finish ...
Process finished with exit code 0