文章目录
观看《Java并发编程的艺术》所做笔记
Java中的并发工具类
并发包中常用的工具类: CountDownLatch,CyclicBarrier,Semaphore,Exchanger
CountDownLatch
CountDownLatch的使用
CountDownLatch
是一个计数器,可以通过构造器来设置计数值(N)
CountDownLatch
执行N个任务可以是一个线程的N个任务,也可以是N个线程中的1个任务
调用countDown()
时计数数量-1
如果计数数量不为0时,执行到await()
会被阻塞,直到计数量为0时才会继续执行
测试案例–1个线程中的N个任务
@Test
public void test() {
CountDownLatch countDownLatch = new CountDownLatch(7);
for (int i = 1; i < 8; i++) {
System.out.println("集齐第" + i + "颗龙珠");
countDownLatch.countDown();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("7颗龙珠已经集齐召唤神龙");
}
CountDownLatch部分源码分析
CountDownLatch构造器
//CountDownLatch
public CountDownLatch(int count) {
//检查计数量是否合理
if (count < 0) throw new IllegalArgumentException("count < 0");
//sync是继承AQS实现部分抽象方法,管理同步状态的同步器
this.sync = new Sync(count);
}
Sync(int count) {
//将计数量设置为同步状态数
setState(count);
}
//AbstractQueuedSynchronizer
protected final void setState(int newState) {
state = newState;
}
CountDownLatch的countDown方法
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//tryReleaseShared尝试释放同步状态
if (tryReleaseShared(arg)) {
//doReleaseShared做释放成功后的一些后继操作:唤醒同步队列中的后继节点等..
doReleaseShared();
return true;
}
return false;
}
//tryReleaseShared()方法是 CountDownLatch内部类Sync实现AQS的方法
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
//CAS+失败重试更新同步状态-1 (实际上就是计数量-1)
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
CountDownLatch的await方法
public void await() throws InterruptedException {
//调用AQS的(响应中断)共享式获取同步状态
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//如果线程中断则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//tryAcquireShared尝试共享式获取同步状态 由CountDownLatch中继承AQS的内部类Sync实现
if (tryAcquireShared(arg) < 0)
//同步状态不为0时则进入构建节点加入同步队列进入自旋...等
doAcquireSharedInterruptibly(arg);
}
//查看同步状态是否为0
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
总结
-
CountDownLatch是一个执行N任务的计数器 通过构造器设置计数量N 等同于 设置同步状态数为N
-
执行完一个任务调用CountDownLatch的countDown方法,计数量就会减一 底层采用AQS释放一个同步状态
-
执行到CountDownLatch的await方法时,如果计数量不为0则阻塞,为0则可继续执行 底层采用AQS获取(并不是真正意义上的获取而是同步状态不为0时像没获取到同步状态一样生成节点加入同步队列进行自旋,阻塞)
CyclicBarrier
Cyclic Barrier的使用
CyclicBarrier
是可以循环使用的屏障 让一组线程都达到一个同步点(屏障)才能继续执行否则被阻塞
通过构造器设置N个线程,当线程执行完任务时,执行CyclicBarrier的await方法时,就会阻塞,直到N个线程都处理完任务才继续执行
Cyclic Barrier的应用场景可以是多线程统计金额,使用Cyclic Barrier等所有线程统计完金额后再总计所有金额
代码演示
public class CyclicBarrierTest implements Runnable{
private CyclicBarrier cb = new CyclicBarrier(5);
private Executor executor = Executors.newFixedThreadPool(5);
private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
public void calc(){
for (int i = 0; i < 5; i++) {
executor.execute(()->{
//省略计算 计算金额为1
map.put(Thread.currentThread().getName(),1);
try {
//执行完计算 使用循环屏障 阻塞线程
cb.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
}
}
@Override
public void run() {
int sum=0;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
sum += entry.getValue();
}
System.out.println("总计:"+sum);
}
@Test
public void test(){
CyclicBarrierTest test = new CyclicBarrierTest();
test.calc();
executor.execute(test);
}
}
/*
总计:5
*/
Cyclic Barrier的另一个构造器可以在所有线程达到屏障时优先执行传入构造器的那个线程
Cyclic Barrier 达到屏障时优先执行构造器中的线程
//其他代码与上面代码演示一致
private CyclicBarrier cb = new CyclicBarrier(5,()->{
System.out.println("所有线程统计完毕,现在开始总计");
});
/*
所有线程统计完毕,现在开始总计
总计:5
*/
Cyclic Barrier
还有reset():重置计数器
,isBroken():阻塞线程是否被中断(当前屏障是否被破坏)
等方法
Cyclic Barrier部分源码分析
构造器
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
//parties:总共需要parties个线程到达屏障
this.parties = parties;
//count:还剩count个线程没到屏障
this.count = parties;
//barriercommand是parties个线程到达屏障后优先执行的Runnable命令
this.barrierCommand = barrierAction;
}
reset()
重置计数器
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//破坏屏障
breakBarrier(); // break the current generation
//使用新的代
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
private void breakBarrier() {
//屏障被破坏标识符改为true
generation.broken = true;
//更新count 唤醒所有线程
count = parties;
trip.signalAll();
}
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
// 更新count 使用新的代
count = parties;
generation = new Generation();
}
Generation代是Cyclic Barrier的内部类 只有一个字段用来标识屏障是否被破坏
private static class Generation {
boolean broken = false;
}
isBroken()
查看当前代破坏屏障的标识
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
getNumberWaiting ()
获得阻塞的线程
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}
await()
public int await() throws InterruptedException, BrokenBarrierException {
try {
//不使用超时等待
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//获得当前代
final Generation g = generation;
//如果屏障破坏抛出异常
if (g.broken)
throw new BrokenBarrierException();
//如果线程中断 则破坏屏障 并 抛出异常
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//当index为0时则说明所有线程到达屏障
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
//优先执行屏障命令
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
//更新下一代(会唤醒所有线程)
nextGeneration();
return 0;
} finally {
//如果执行屏障命令发生中断(未执行 ranAction = true;)则破坏屏障
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
//未使用超时等待则一直等待
if (!timed)
trip.await();
//使用超时等待则等待规定时间
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
//如果屏障被破坏抛出异常
if (g.broken)
throw new BrokenBarrierException();
//如果不是当前代则返回下标
if (g != generation)
return index;
//如果使用超时等待且超时则破坏屏障抛出异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
总结
-
Cyclic Barrier
底层使用reentrantlock和condition实现 等待/通知模式 -
使用Generation代来标识屏障是否被破坏 只有线程被中断时才会破坏屏障
-
可以在构造器中传入所有线程到达屏障后优先执行的Runnable命令
-
CountDownLatch只能使用一次而Cyclic Barrier可以重置计数器
Semaphore
Semaphore的使用
Semaphore 信号量 控制同时访问某资源的线程个数
public class SemaphoreTest {
public static void main(String[] args) {
Semaphore s = new Semaphore(2);
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 4; i++) {
executor.execute(()->{
try {
s.acquire();
System.out.println(Thread.currentThread().getName()+"获得资源");
//执行任务
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+"释放资源======");
s.release();
}
});
}
executor.shutdown();
}
}
/*
pool-1-thread-2获得资源
pool-1-thread-1获得资源
pool-1-thread-1释放资源======
pool-1-thread-2释放资源======
pool-1-thread-3获得资源
pool-1-thread-4获得资源
pool-1-thread-4释放资源======
pool-1-thread-3释放资源======
*/
Semaphore部分源码分析
Semaphore底层使用共享锁,信号量就是共享锁同步状态数
构造器
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
当要访问某资源的线程数大于semaphore信号量时会阻塞后面的线程,直到前面线程释放同步状态(信号量许可证)
一旦出现空闲的信号量许可证(同步状态)则可以抢占,抢占方式可以通过构造器设置公平锁或非公平锁 默认非公平锁
acquire
获取信号量许可证 获取同步状态
public void acquire() throws InterruptedException {
//响应中断的共享式获取同步状态
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//如果线程中断则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquireShared(arg)返回的是这次获取同步状态后剩余的同步状态
// 如果返回值<0 说明没有同步状态(没有信号量许可证了要阻塞) 其他情况(拿到了许可证)直接退出
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
//得到同步状态 (获得剩余信号量许可证个数)
int available = getState();
//remaining(这次获取后剩下的个数) = 当前有的 - 1
int remaining = available - acquires;
//如果剩下的大于等于0才CAS更新同步状态 否则返回
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
release
释放信号量许可证 释放同步状态
public void release() {
//共享式释放
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//tryReleaseShared(arg)尝试释放同步状态
if (tryReleaseShared(arg)) {
//成功释放同步状态后的后续操作(唤醒后继节点...)
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
//current: 当前同步状态
int current = getState();
//next = 当前同步状态 + 1
int next = current + releases;
//如果溢出抛出异常
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
//CAS更新同步状态
if (compareAndSetState(current, next))
return true;
}
}
availablePermits
返回当前可用信号量许可证个数
因为信号量许可证就是同步状态,所以实际上就是返回同步状态数
public int availablePermits() {
return sync.getPermits();
}
final int getPermits() {
return getState();
}
protected final int getState() {
return state;
}
getQueueLength
获得正在等待获取信号量许可证的线程个数
实际上就是统计同步队列中的节点个数
public final int getQueueLength() {
return sync.getQueueLength();
}
public final int getQueueLength() {
int n = 0;
for (Node p = tail; p != null; p = p.prev) {
if (p.thread != null)
++n;
}
return n;
}
hasQueuedThreads
是否有正在等待获取许可证的线程
实际上就是判断同步队列是否为空 (首尾节点是否相等)
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
public final boolean hasQueuedThreads() {
return head != tail;
}
总结
- Semaphore信号量底层采用AQS+共享锁 设置的信号量数就是同步状态数,默认使用非公平锁
- 常用于有限的特殊资源场景如:数据库连接
Exchanger
Exchanger的使用
Exchanger交换者用于线程间协作的工具类
假设第一个执行exchange的是线程A 第二个执行exchange的是线程B
线程A: 数据B = exchange(数据A) 可以把数据A传给线程B 然后通过这个方法得到线程B传来的数据 以此实现数据交换
线程B: 数据A = exchange(数据B)
public class ExchangerTest {
private Exchanger<String> exchanger = new Exchanger();
@Test
public void testExchange() {
new Thread(() -> {
String A = "银行流水A";
try {
System.out.println(exchanger.exchange(A));//银行流水B
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
String B = "银行流水B";
try {
String A = exchanger.exchange(B);//银行流水A
System.out.println("A=" + A + " B=" + B);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*
银行流水B
A=银行流水A B=银行流水B
*/