3.并发工具类
Java并发工具类需要依赖对于Java锁机制的理解,尤其是AQS,可以参见前文:
Java锁机制浅析(一)
Java锁机制浅析(二)之AQS
有了AQS这个并发同步基础类之后,基于AQS实现很多实用的工具类,前面已经分析了ReentrantLock
和ReentrantReadWriteLock
接下来分析CountDownLatch
、CyclicBarrier
、Semaphore
。
3.1 CountDownLatch 计数器
3.1.1主要API
方法 | 说明 |
---|---|
CountDownLatch(int count) | 指定计数量的构造函数。 |
void await() | 导致当前线程等到锁存器计数到零,除非线程是 interrupted |
boolean await(long timeout, TimeUnit unit) | 使当前线程等待直到锁存器计数到零为止,除非线程为 interrupted或指定的等待时间过去 |
void countDown() | 减少锁存器的计数,如果计数达到零,释放所有等待的线程 |
long getCount() | 返回当前计数 |
3.1.2 Demo
CountDownLatch 通常用在等待多个并发任务执行完成后,再继续后续操作的场景中。CountDownLatch的计数不能被重置,它是一个一次性的计数器。
线程调用await()
函数后将被阻塞,直到其他线程调用countDown()
内部的count减少到零后,被await()
阻塞的线程才会被放行。
3.1.2.1 子线程并发统计,主线程对统计结果进行合并
下面的Demo中,模拟3个线程并发统计,主线程在3个线程统计结束后将统计结果汇总。
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception {
CountDownLatch c = new CountDownLatch(3); //计数为3
int[] ary = new int[]{0,0,0}; //模拟多个线程统计结果
long start = new Date().getTime();
System.out.println("并行统计开始...");
//任务1统计出数量为10
new Thread(() -> {
try {
Thread.sleep(3000); //模拟线程耗时
ary[0] = 10;
c.countDown();
System.out.println("线程1统计完成,耗时:"+(new Date().getTime() - start));
} catch (Exception e) {
e.printStackTrace();
}
}).start();
//任务2统计结果为20
new Thread(() -> {
try {
Thread.sleep(4000);//模拟线程耗时
ary[1] = 20;
c.countDown();
System.out.println("线程2统计完成,耗时:"+(new Date().getTime() - start));
} catch (Exception e) {
e.printStackTrace();
}
}).start();
//任务3统计结果为30
new Thread(() -> {
try {
Thread.sleep(10000);//模拟线程耗时
ary[2] = 30;
c.countDown();
System.out.println("线程3统计完成,耗时:"+(new Date().getTime() - start));
} catch (Exception e) {
e.printStackTrace();
}
}).start();
//主线程阻塞,直到所有并发任务执行结束后 对统计结果进行加和
c.await();
System.out.println("并行统计结束,总共耗时"+(new Date().getTime() - start));
int sum = 0;
for(int i : ary){
sum += i;
}
System.out.println("统计结果汇总:"+sum);
}
}
执行结果:
并行统计开始…
线程1统计完成,耗时:3111
线程2统计完成,耗时:4108
线程3统计完成,耗时:10112
并行统计结束,总共耗时10112
统计结果汇总:60
3.1.2.2 模拟压力测试设置集合点
public class CountDownLatchDemo2 {
public static void main(String[] args) throws Exception {
int n = 10;
CountDownLatch single = new CountDownLatch(1);
CountDownLatch count = new CountDownLatch(n);
for (int i = 0; i < n; i++) {
new Thread(new LoadTest(single, count)).start();
}
//在single.countDown()调用前,所有子线程阻塞在single.await()
single.countDown(); //通知子线程可以发送交易。
count.await(); //等待子线程交易完成
}
static class LoadTest implements Runnable {
private CountDownLatch single;
private CountDownLatch count;
public LoadTest(CountDownLatch single, CountDownLatch count) {
this.single = single;
this.count = count;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + ":到达集合点...");
single.await();
execute();
System.out.println(Thread.currentThread().getName() + ":测试完毕...");
count.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
//模拟测试交易占用时间
private void execute() throws Exception {
Thread.sleep(ThreadLocalRandom.current().nextLong(5000));
}
}
}
3.1.2 源码
CountDownLatch
最核心的两个API都是通过内部Sync继承AQS来实现的;理解了AQS的原理之后,CountDownLatch的思路就非常清晰了。
/**
* 同步等待,通过AQS可中断共享锁方法实现。
* 获取锁的逻辑最终由内部类Sync的tryAcquireShared()方法实现。
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* 带超时的同步等待。通过AQS的带超时共享锁方法实现
* 获取锁的逻辑最终由内部类Sync的tryAcquireShared()方法实现。
*/
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* 释放锁逻辑,通过AQS释放共享锁方法实现。
* 释放锁的逻辑最终由内部类Sync的tryAcquireShared()方法实现。
*/
public void countDown() {
sync.releaseShared(1);
}
/**
* 构造函数,使用count初始化sync
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
private final Sync sync;
private static final class Sync extends AbstractQueuedSynchronizer {
//构造函数,将AQS的状态值初始为count
Sync(int count) { setState(count); }
//返回AQS的当前状态值
int getCount() { return getState(); }
/**
* AQS共享锁获取逻辑,由CountDownLatch.await()间接调用
* 如果AQS状态值为零则返回1(成功获取锁),否则返回-1(获取锁失败)
*/
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
/**
* AQS共享锁获释放,由CountDownLatch.countDown()间接调用
* 循环+CAS(递减AQS状态值,直到状态值为零)
*/
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}