Java并发编程中的数据结构与同步工具
1. 累加器类
1.1 DoubleAdder类
DoubleAdder类没有
increment()
和
decrement()
方法。
1.2 LongAccumulator和DoubleAccumulator类
这两个类相似,但有一个重要区别。它们的构造函数需要指定两个参数:
- 内部计数器的初始值。
- 用于将新值累加到累加器的函数。
注意,该函数不能依赖于累加顺序。这两个类最重要的方法有:
-
accumulate()
:接收一个长整型值作为参数,将该函数应用于当前值和参数,以增加或减少计数器。
-
get()
:返回计数器的当前值。
示例代码如下:
LongAccumulator accumulator=new LongAccumulator((x,y) -> x*y, 1);
IntStream.range(1, 10).parallel().forEach(x -> accumulator.accumulate(x));
System.out.println(accumulator.get());
在累加器中使用了可交换操作,因此任何输入顺序的结果都相同。
2. 同步机制
2.1 同步类型
在并发应用程序中,有两种同步类型:
- 进程同步:用于控制任务的执行顺序。例如,一个任务必须等待其他任务完成后才能开始执行。
- 数据同步:当两个或多个任务访问同一个内存对象时使用。在这种情况下,必须保护对该对象的写操作,否则可能会出现数据竞争条件,导致程序的最终结果在每次执行时都不同。
2.2 Java中的同步机制
Java并发API提供了实现这两种同步的机制,最基本的同步机制是
synchronized
关键字,它可以应用于方法或代码块:
- 应用于方法时,同一时间只有一个线程可以执行该方法。
- 应用于代码块时,需要指定一个对象引用,同一时间只有一个受该对象保护的代码块可以执行。
Java还提供了其他同步机制:
| 同步机制 | 描述 |
| ---- | ---- |
| Lock接口及其实现类 | 用于实现临界区,确保同一时间只有一个线程执行该代码块 |
| Semaphore类 | 实现了Edsger Dijkstra引入的著名信号量同步机制 |
| CountDownLatch | 允许一个或多个线程等待其他线程完成 |
| CyclicBarrier | 允许在一个公共点同步不同的任务 |
| Phaser | 用于实现分阶段的并发任务 |
| Exchanger | 用于实现两个任务之间的数据交换点 |
| CompletableFuture | Java 8引入的新特性,扩展了执行器任务的Future机制,以异步方式生成任务的结果 |
2.3 CommonTask类
该类用于使调用线程在0到10秒之间的随机时间段内休眠,模拟任务的执行时间。代码如下:
public class CommonTask {
public static void doTask() {
long duration = ThreadLocalRandom.current().nextLong(10);
System.out.printf("%s-%s: Working %d seconds\n",new Date(),Thread.currentThread().getName(),duration);
try {
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.4 Lock接口
最基本的同步机制之一是
Lock
接口及其实现类,基本实现类是
ReentrantLock
类。可以使用该类轻松实现临界区。示例代码如下:
public class LockTask implements Runnable {
private static ReentrantLock lock = new ReentrantLock();
private String name;
public LockTask(String name) {
this.name=name;
}
@Override
public void run() {
try {
lock.lock();
System.out.println("Task: " + name + "; Date: " + new Date() + ": Running the task");
CommonTask.doTask();
System.out.println("Task: " + name + "; Date: " + new Date() + ": The execution has finished");
} finally {
lock.unlock();
}
}
}
可以使用以下代码在执行器中执行十个任务来验证:
public class LockMain {
public static void main(String[] args) {
ThreadPoolExecutor executor=(ThreadPoolExecutor) Executors.newCachedThreadPool();
for (int i=0; i<10; i++) {
executor.execute(new LockTask("Task "+i));
}
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.5 Semaphore类
信号量机制由Edsger Dijkstra在1962年引入,用于控制对一个或多个共享资源的访问。该机制基于一个内部计数器和两个方法
wait()
和
signal()
。
在Java中,信号量由
Semaphore
类实现,
wait()
方法称为
acquire()
,
signal()
方法称为
release()
。示例代码如下:
public class SemaphoreTask implements Runnable{
private Semaphore semaphore;
public SemaphoreTask(Semaphore semaphore) {
this.semaphore=semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
CommonTask.doTask();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
}
在主程序中,可以执行十个任务,共享一个初始化为两个共享资源的
Semaphore
类,这样将有两个任务同时运行:
public static void main(String[] args) {
Semaphore semaphore=new Semaphore(2);
ThreadPoolExecutor executor=(ThreadPoolExecutor) Executors.newCachedThreadPool();
for (int i=0; i<10; i++) {
executor.execute(new SemaphoreTask(semaphore));
}
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.6 CountDownLatch类
该类提供了一种机制,用于等待一个或多个并发任务的完成。它有一个内部计数器,必须用要等待的任务数量进行初始化。
await()
方法会使调用线程休眠,直到内部计数器达到零,
countDown()
方法会减少内部计数器。
示例代码如下:
public class CountDownTask implements Runnable {
private CountDownLatch countDownLatch;
public CountDownTask(CountDownLatch countDownLatch) {
this.countDownLatch=countDownLatch;
}
@Override
public void run() {
CommonTask.doTask();
countDownLatch.countDown();
}
}
在
main()
方法中,可以执行任务并使用
CountDownLatch
的
await()
方法等待它们完成:
public static void main(String[] args) {
CountDownLatch countDownLatch=new CountDownLatch(10);
ThreadPoolExecutor executor=(ThreadPoolExecutor) Executors.newCachedThreadPool();
System.out.println("Main: Launching tasks");
for (int i=0; i<10; i++) {
executor.execute(new CountDownTask(countDownLatch));
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
}
2.7 CyclicBarrier类
该类允许在一个公共点同步一些任务。所有任务将在该点等待,直到所有任务都到达。它内部也管理一个计数器,记录尚未到达该点的任务数量。当一个任务到达指定点时,必须执行
await()
方法等待其他任务。当所有任务都到达时,
CyclicBarrier
对象会唤醒它们,使它们继续执行。
示例代码如下:
public class BarrierTask implements Runnable {
private CyclicBarrier barrier;
public BarrierTask(CyclicBarrier barrier) {
this.barrier=barrier;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+": Phase 1");
CommonTask.doTask();
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+": Phase 2");
}
}
还可以实现另一个
Runnable
对象,当所有任务都执行了
await()
方法时,由
CyclicBarrier
执行:
public class FinishBarrierTask implements Runnable {
@Override
public void run() {
System.out.println("FinishBarrierTask: All the tasks have finished");
}
}
在
main()
方法中,可以执行十个任务,并使用
CyclicBarrier
进行同步:
public static void main(String[] args) {
CyclicBarrier barrier=new CyclicBarrier(10,new FinishBarrierTask());
ThreadPoolExecutor executor=(ThreadPoolExecutor) Executors.newCachedThreadPool();
for (int i=0; i<10; i++) {
executor.execute(new BarrierTask(barrier));
}
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.8 CompletableFuture类
这是Java 8并发API中引入的一种新的同步机制,扩展了
Future
机制,使其更强大和灵活。它允许实现事件驱动模型,将任务链接起来,只有在前一个任务完成后,后续任务才会执行。
2.8.1 创建CompletableFuture
可以使用构造函数创建
CompletableFuture
,也可以使用
runAsync()
或
supplyAsync()
方法创建:
-
runAsync()
:执行一个
Runnable
对象,返回
CompletableFuture<Void>
,该计算不能返回任何结果。
-
supplyAsync()
:执行一个
Supplier
接口的实现,该接口的
get()
方法包含任务的代码并返回结果。
2.8.2 任务执行顺序方法
CompletableFuture
类提供了许多方法来组织任务的执行顺序,实现事件驱动模型:
-
thenApplyAsync()
:接收一个
Function
接口的实现作为参数,当调用的
CompletableFuture
完成时执行该函数,返回一个
CompletableFuture
以获取该函数的结果。
-
thenComposeAsync()
:类似于
thenApplyAsync
,但当提供的函数也返回
CompletableFuture
时很有用。
-
thenAcceptAsync()
:类似于
thenApplyAsync
,但参数是一个
Consumer
接口的实现,该计算不返回结果。
-
thenRunAsync()
:接收一个
Runnable
对象作为参数。
-
thenCombineAsync()
:接收两个参数,一个是另一个
CompletableFuture
实例,另一个是
BiFunction
接口的实现,当两个
CompletableFuture
都完成时执行该
BiFunction
。
-
runAfterBothAsync()
:接收两个参数,一个是另一个
CompletableFuture
,另一个是
Runnable
接口的实现,当两个
CompletableFuture
都完成时执行该
Runnable
。
-
runAfterEitherAsync()
:当其中一个
CompletableFuture
对象完成时,执行
Runnable
任务。
-
allOf()
:接收一个可变的
CompletableFuture
对象列表,返回一个
CompletableFuture<Void>
,当所有
CompletableFuture
对象都完成时返回结果。
-
anyOf()
:类似于
allOf
,但当其中一个
CompletableFuture
完成时返回结果。
2.8.3 获取结果
可以使用
get()
或
join()
方法获取
CompletableFuture
返回的结果:
-
get()
:抛出
ExecutionException
,这是一个受检查的异常。
-
join()
:抛出
RuntimeException
,这是一个未受检查的异常。
大多数方法带有
Async
后缀,表示这些方法将使用
ForkJoinPool.commonPool
实例并发执行。没有
Async
后缀的方法将串行执行,带有
Async
后缀且有一个执行器实例作为额外参数的方法,
CompletableFuture
将在该执行器中异步执行。
2.9 使用CompletableFuture类
2.9.1 任务树
使用一个包含20,000个亚马逊产品的集合,实现以下任务树:
graph LR
A[Reading the Examples] --> B[Searching The Data]
A --> C[Get the Users]
A --> D[Best Rated Product]
A --> E[Best Selling Product]
B --> F[Writing Search Results]
D --> G[Best Product Data]
E --> G
2.9.2 辅助任务
-
LoadTask:从磁盘加载产品信息并返回一个Product对象列表。
public class LoadTask implements Supplier<List<Product>> {
private Path path;
public LoadTask (Path path) {
this.path=path;
}
@Override
public List<Product> get() {
List<Product> productList=null;
try {
productList = Files.walk(path, FileVisitOption.FOLLOW_LINKS).parallel()
.filter(f -> f.toString().endsWith(".txt"))
.map(ProductLoader::load).collect(Collectors.toList());
} catch (IOException e) {
e.printStackTrace();
}
return productList;
}
}
-
SearchTask:在Product对象列表中搜索包含指定单词的产品。
public class SearchTask implements Function<List<Product>, List<Product>> {
private String query;
public SearchTask(String query) {
this.query=query;
}
@Override
public List<Product> apply(List<Product> products) {
System.out.println(new Date()+": CompletableTask: start");
List<Product> ret = products.stream()
.filter(product -> product.getTitle().toLowerCase().contains(query))
.collect(Collectors.toList());
System.out.println(new Date()+": CompletableTask: end: "+ret.size());
return ret;
}
}
-
WriteTask:将搜索结果写入文件。
public class WriteTask implements Consumer<List<Product>> {
@Override
public void accept(List<Product> products) {
// implementation is omitted
}
}
2.9.3 main()方法
public class CompletableMain {
public static void main(String[] args) {
Path file = Paths.get("data","category");
System.out.println(new Date() + ": Main: Loading products");
LoadTask loadTask = new LoadTask(file);
CompletableFuture<List<Product>> loadFuture = CompletableFuture.supplyAsync(loadTask);
System.out.println(new Date() + ": Main: Then apply for search");
CompletableFuture<List<Product>> completableSearch = loadFuture.thenApplyAsync(new SearchTask("love"));
CompletableFuture<Void> completableWrite = completableSearch.thenAcceptAsync(new WriteTask());
completableWrite.exceptionally(ex -> {
System.out.println(new Date() + ": Main: Exception " + ex.getMessage());
return null;
});
System.out.println(new Date() + ": Main: Then apply for users");
CompletableFuture<List<String>> completableUsers = loadFuture.thenApplyAsync(resultList -> {
System.out.println(new Date() + ": Main: Completable users: start");
List<String> users = resultList.stream()
.flatMap(p -> p.getReviews().stream())
.map(review -> review.getUser())
.distinct()
.collect(Collectors.toList());
System.out.println(new Date() + ": Main: Completable users: end");
return users;
});
System.out.println(new Date() + ": Main: Then apply for best rated product....");
CompletableFuture<Product> completableProduct = loadFuture.thenApplyAsync(resultList -> {
Product maxProduct = null;
double maxScore = 0.0;
System.out.println(new Date() + ": Main: Completable product: start");
for (Product product : resultList) {
if (!product.getReviews().isEmpty()) {
double score = product.getReviews().stream()
.mapToDouble(review -> review.getValue())
.average().getAsDouble();
if (score > maxScore) {
maxProduct = product;
maxScore = score;
}
}
}
System.out.println(new Date() + ": Main: Completable product: end");
return maxProduct;
});
System.out.println(new Date() + ": Main: Then apply for best selling product....");
CompletableFuture<Product> completableBestSellingProduct = loadFuture.thenApplyAsync(resultList -> {
System.out.println(new Date() + ": Main: Completable best selling: start");
Product bestProduct = resultList.stream()
.min(Comparator.comparingLong(Product::getSalesrank))
.orElse(null);
System.out.println(new Date() + ": Main: Completable best selling: end");
return bestProduct;
});
CompletableFuture<String> completableProductResult = completableBestSellingProduct.thenCombineAsync(
completableProduct,
(bestSellingProduct, bestRatedProduct) -> {
System.out.println(new Date() + ": Main: Completable product result: start");
String ret = "The best selling product is " + bestSellingProduct.getTitle() + "\n";
ret += "The best rated product is " + bestRatedProduct.getTitle();
System.out.println(new Date() + ": Main: Completable product result: end");
return ret;
});
System.out.println(new Date() + ": Main: Waiting for results");
CompletableFuture<Void> finalCompletableFuture = CompletableFuture.allOf(completableProductResult, completableUsers, completableWrite);
finalCompletableFuture.join();
try {
System.out.println("Number of loaded products: " + loadFuture.get().size());
System.out.println("Number of found products: " + completableSearch.get().size());
System.out.println("Number of users: " + completableUsers.get().size());
System.out.println("Best rated product: " + completableProduct.get().getTitle());
System.out.println("Best selling product: " + completableBestSellingProduct.get().getTitle());
System.out.println("Product result: " + completableProductResult.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
主程序执行所有配置并等待任务完成,任务的执行顺序按照配置进行。
3. 代码执行流程分析
3.1 整体执行流程
上述使用
CompletableFuture
实现的任务树,整体执行流程如下:
1. 从磁盘加载产品信息。
2. 并行执行四个任务:
- 搜索包含指定单词的产品,并将结果写入文件。
- 获取购买产品的用户列表。
- 找出评分最高的产品。
- 找出销量最好的产品。
3. 合并评分最高和销量最好的产品信息。
4. 等待所有任务完成并输出结果。
3.2 各步骤详细分析
3.2.1 加载产品信息
Path file = Paths.get("data","category");
System.out.println(new Date() + ": Main: Loading products");
LoadTask loadTask = new LoadTask(file);
CompletableFuture<List<Product>> loadFuture = CompletableFuture.supplyAsync(loadTask);
-
使用
LoadTask从磁盘加载产品信息。 -
supplyAsync方法异步执行LoadTask,返回一个CompletableFuture对象loadFuture。
3.2.2 搜索产品并写入结果
System.out.println(new Date() + ": Main: Then apply for search");
CompletableFuture<List<Product>> completableSearch = loadFuture.thenApplyAsync(new SearchTask("love"));
CompletableFuture<Void> completableWrite = completableSearch.thenAcceptAsync(new WriteTask());
completableWrite.exceptionally(ex -> {
System.out.println(new Date() + ": Main: Exception " + ex.getMessage());
return null;
});
-
thenApplyAsync方法在loadFuture完成后,异步执行SearchTask,搜索包含 “love” 的产品。 -
thenAcceptAsync方法在搜索完成后,异步执行WriteTask,将搜索结果写入文件。 -
exceptionally方法处理WriteTask可能抛出的异常。
3.2.3 获取用户列表
System.out.println(new Date() + ": Main: Then apply for users");
CompletableFuture<List<String>> completableUsers = loadFuture.thenApplyAsync(resultList -> {
System.out.println(new Date() + ": Main: Completable users: start");
List<String> users = resultList.stream()
.flatMap(p -> p.getReviews().stream())
.map(review -> review.getUser())
.distinct()
.collect(Collectors.toList());
System.out.println(new Date() + ": Main: Completable users: end");
return users;
});
-
thenApplyAsync方法在loadFuture完成后,异步执行一个 lambda 表达式,获取购买产品的用户列表。
3.2.4 找出评分最高和销量最好的产品
System.out.println(new Date() + ": Main: Then apply for best rated product....");
CompletableFuture<Product> completableProduct = loadFuture.thenApplyAsync(resultList -> {
Product maxProduct = null;
double maxScore = 0.0;
System.out.println(new Date() + ": Main: Completable product: start");
for (Product product : resultList) {
if (!product.getReviews().isEmpty()) {
double score = product.getReviews().stream()
.mapToDouble(review -> review.getValue())
.average().getAsDouble();
if (score > maxScore) {
maxProduct = product;
maxScore = score;
}
}
}
System.out.println(new Date() + ": Main: Completable product: end");
return maxProduct;
});
System.out.println(new Date() + ": Main: Then apply for best selling product....");
CompletableFuture<Product> completableBestSellingProduct = loadFuture.thenApplyAsync(resultList -> {
System.out.println(new Date() + ": Main: Completable best selling: start");
Product bestProduct = resultList.stream()
.min(Comparator.comparingLong(Product::getSalesrank))
.orElse(null);
System.out.println(new Date() + ": Main: Completable best selling: end");
return bestProduct;
});
-
分别使用
thenApplyAsync方法在loadFuture完成后,异步执行两个 lambda 表达式,找出评分最高和销量最好的产品。
3.2.5 合并产品信息
CompletableFuture<String> completableProductResult = completableBestSellingProduct.thenCombineAsync(
completableProduct,
(bestSellingProduct, bestRatedProduct) -> {
System.out.println(new Date() + ": Main: Completable product result: start");
String ret = "The best selling product is " + bestSellingProduct.getTitle() + "\n";
ret += "The best rated product is " + bestRatedProduct.getTitle();
System.out.println(new Date() + ": Main: Completable product result: end");
return ret;
});
-
thenCombineAsync方法在评分最高和销量最好的产品任务都完成后,异步执行一个 lambda 表达式,合并两个产品的信息。
3.2.6 等待所有任务完成并输出结果
System.out.println(new Date() + ": Main: Waiting for results");
CompletableFuture<Void> finalCompletableFuture = CompletableFuture.allOf(completableProductResult, completableUsers, completableWrite);
finalCompletableFuture.join();
try {
System.out.println("Number of loaded products: " + loadFuture.get().size());
System.out.println("Number of found products: " + completableSearch.get().size());
System.out.println("Number of users: " + completableUsers.get().size());
System.out.println("Best rated product: " + completableProduct.get().getTitle());
System.out.println("Best selling product: " + completableBestSellingProduct.get().getTitle());
System.out.println("Product result: " + completableProductResult.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
-
allOf方法创建一个新的CompletableFuture对象,等待所有任务完成。 -
join方法阻塞主线程,直到所有任务完成。 -
使用
get方法获取各个任务的结果并输出。
3.3 总结
通过使用
CompletableFuture
,可以方便地实现异步任务的并发执行和任务之间的依赖关系。在实际开发中,可以根据具体需求,灵活运用
CompletableFuture
的各种方法,实现复杂的任务调度和处理。
4. 同步机制对比
4.1 不同同步机制的特点
| 同步机制 | 特点 | 适用场景 |
|---|---|---|
synchronized
关键字
| 简单易用,可应用于方法或代码块 | 简单的同步需求,如保护单个方法或代码块 |
Lock
接口及其实现类
| 功能强大,可实现更复杂的同步逻辑 | 需要更细粒度的同步控制,如可重入锁、公平锁等 |
Semaphore
类
| 基于信号量机制,控制对共享资源的访问 | 限制对有限资源的并发访问 |
CountDownLatch
| 允许一个或多个线程等待其他线程完成 | 一个或多个线程需要等待一组任务完成后再继续执行 |
CyclicBarrier
| 在公共点同步任务 | 多个任务需要在某个点同步后再继续执行 |
Phaser
| 实现分阶段的并发任务 | 任务可以划分为多个阶段,每个阶段需要同步 |
Exchanger
| 实现两个任务之间的数据交换 | 两个任务需要交换数据的场景 |
CompletableFuture
|
扩展了
Future
机制,支持事件驱动模型
| 异步任务的并发执行和任务之间的依赖关系 |
4.2 选择合适的同步机制
在选择同步机制时,需要考虑以下因素:
-
同步需求的复杂度
:如果同步需求简单,使用
synchronized
关键字即可;如果需要更复杂的同步逻辑,可选择
Lock
接口及其实现类。
-
资源的并发访问控制
:如果需要限制对共享资源的并发访问,可使用
Semaphore
类。
-
任务的同步点
:如果任务需要在某个点同步,可使用
CyclicBarrier
或
Phaser
;如果一个或多个线程需要等待其他线程完成,可使用
CountDownLatch
。
-
任务的异步执行和依赖关系
:如果需要实现异步任务的并发执行和任务之间的依赖关系,可使用
CompletableFuture
。
5. 注意事项
5.1 锁的使用
-
使用
Lock接口及其实现类时,必须在finally块中释放锁,以确保锁一定会被释放,避免死锁。
try {
lock.lock();
// 临界区代码
} finally {
lock.unlock();
}
5.2 异常处理
-
在使用
CompletableFuture时,要注意异常处理。可以使用exceptionally方法处理任务执行过程中抛出的异常。
completableWrite.exceptionally(ex -> {
System.out.println(new Date() + ": Main: Exception " + ex.getMessage());
return null;
});
5.3 资源管理
-
在使用
Semaphore类时,要确保在使用完共享资源后调用release()方法,以释放资源。
try {
semaphore.acquire();
// 使用共享资源
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
6. 总结
本文介绍了 Java 并发编程中的数据结构与同步工具,包括累加器类、各种同步机制以及
CompletableFuture
的使用。通过合理使用这些工具,可以实现高效、安全的并发编程。在实际开发中,需要根据具体需求选择合适的同步机制,并注意锁的使用、异常处理和资源管理等问题。
通过本文的学习,你应该对 Java 并发编程有了更深入的理解,能够运用这些知识解决实际开发中的并发问题。希望本文对你有所帮助!
超级会员免费看

被折叠的 条评论
为什么被折叠?



