java.util.concurrent 及其子包,集中了 Java 并发的各种基础工具类,具体主要包括几个方面:
1)提供了比 synchronized 更加高级的各种同步结构,包括 CountDownLatch、CyclicBarrier、Semaphore 等,可以实现更加丰富的多线程操作,比如利用 Semaphore 作为资源控制器,限制同时进行工作的线程数量。
2)各种线程安全的容器,比如最常见的 ConcurrentHashMap、有序的 ConcurrentSkipListMap,或者通过类似快照机制,实现线程安全的动态数组 CopyOnWriteArrayList 等。各种并发队列实现,如各种 BlockingQueue 实现,比较典型的 ArrayBlockingQueue、 SynchronousQueue 或针对特定场景的 PriorityBlockingQueue 等。
3)强大的 Executor 框架,可以创建各种不同类型的线程池,调度任务运行等,绝大部分情况下,不再需要自己从头实现线程池和任务调度器。
1、
CountDownLatch,允许一个或多个线程等待某些操作完成。
CyclicBarrier,一种辅助性的同步结构,允许多个线程等待到达某个屏障。
Semaphore,Java 版本的信号量实现。: Semaphore 就是个计数器,其基本逻辑基于 acquire/release,并没有太复杂的同步逻辑。
CountDownLatch 是不可以重置的,所以无法重用;而 CyclicBarrier 则没有这种限制,可以重用。
CountDownLatch 的基本操作组合是 countDown/await。调用 await 的线程阻塞等待 countDown 足够的次数,不管你是在一个线程还是多个线程里 countDown,只要次数足够即可。所以就像 Brain Goetz 说过的,CountDownLatch 操作的是事件。CyclicBarrier 的基本操作组合,则就是 await,当所有的伙伴(parties)都调用了 await,才会继续进行任务,并自动进行重置。注意,正常情况下,CyclicBarrier 的重置都是自动发生的,如果我们调用 reset 方法,但还有线程在等待,就会导致等待线程被打扰,抛出 BrokenBarrierException 异常。CyclicBarrier 侧重点是线程,而不是调用事件,它的典型应用场景是用来等待并发线程结束。
上一段CountDownLatch工作中的应用:两个数据源开两个线程,等待都结束后进行数据汇总返回前端报表展示
public class DataCenterServiceImpl implements DataCenterService {
// 其他代码
public CalculateIndicatorsKD getSemSummary(Date fromDate, Date toDate, String siteIdStr) throws Exception {
SiteDataSqlSem request = new SiteDataSqlSem(fromDate, toDate);
CountDownLatch countDownLatch = new CountDownLatch(2);
ConcurrentHashMap<String, CalculateIndicatorsKD> datas = new ConcurrentHashMap<>();
new Thread(() -> {
//展现量presentCount、点击量clickCount、现金消费cashAmount
CalculateIndicatorsKD semDatabaseSummary = deliverToolStatisticsDao.getSemSummary(siteIdStr, request.getFromDateStr(), request.getToDateStr());
if (semDatabaseSummary == null) semDatabaseSummary = new CalculateIndicatorsKD();
datas.put("database", semDatabaseSummary);
countDownLatch.countDown();
}).start();
new Thread(() -> {
//签约金额);sign_price,资源总量);clue_count,资源有效);resource_status
CalculateIndicatorsKD semCrmSummary = crmDao.getSemSummary(fromDate, toDate, siteIdStr);
if (semCrmSummary == null) semCrmSummary = new CalculateIndicatorsKD();
datas.put("crm", semCrmSummary);
countDownLatch.countDown();
}).start();
countDownLatch.await(60, TimeUnit.SECONDS);
//展现量presentCount、点击量clickCount、现金消费cashAmount
CalculateIndicatorsKD semDatabaseSummary = datas.get("database");
if (semDatabaseSummary == null) semDatabaseSummary = new CalculateIndicatorsKD();
//签约金额);sign_price,资源总量);clue_count,资源有效);resource_status
CalculateIndicatorsKD semCrmSummary = datas.get("crm");
if (semCrmSummary != null) {
semDatabaseSummary.setQianYueJinE(semCrmSummary.getQianYueJinE());
semDatabaseSummary.setZiYuanZongLiang(semCrmSummary.getZiYuanZongLiang());
semDatabaseSummary.setZiYuanYouXiao(semCrmSummary.getZiYuanYouXiao());
}
return semDatabaseSummary;
}
// 其他代码
}
假设有 10 个人排队,我们将其分成 5 个人一批,通过 CountDownLatch 来协调批次:
import java.util.concurrent.CountDownLatch;
public class LatchSample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(6);
for (int i = 0; i < 5; i++) {
Thread t = new Thread(new FirstBatchWorker(latch));
t.start();
}
for (int i = 0; i < 5; i++) {
Thread t = new Thread(new SecondBatchWorker(latch));
t.start();
}
// 注意这里也是演示目的的逻辑,并不是推荐的协调方式
while ( latch.getCount() != 1 ){
Thread.sleep(100L);
}
System.out.println("Wait for first batch finish");
latch.countDown();
}
}
class FirstBatchWorker implements Runnable {
private CountDownLatch latch;
public FirstBatchWorker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
System.out.println("First batch executed!");
latch.countDown();
}
}
class SecondBatchWorker implements Runnable {
private CountDownLatch latch;
public SecondBatchWorker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
latch.await();
System.out.println("Second batch executed!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
后一批次的线程进行 await,等待前一批 countDown 足够多次。这个例子也从侧面体现出了它的局限性,虽然它也能够支持 10 个人排队的情况,但是因为不能重用
2、
Map 放入或者获取的速度,而不在乎顺序,大多推荐使用 ConcurrentHashMap,反之则使用 ConcurrentSkipListMap;没有 ConcurrentTreeMap是因为线程安全并且红黑树平衡太困难,用ConcurrentSkipListMap替代
CopyOnWriteArraySet 是通过包装了 CopyOnWriteArrayList 来实现,CopyOnWrite的意思是任何修改操作都会拷贝原数组,修改后替换原来的数组