Java CompletableFuture因在上下文中使用共享变量,导致线程安全问题

背景

在使用CompletableFuture.supplyAsync()时,多个异步中,同时共用的一个查询对象参数,而且在这多个任务中间会穿插地对这个对象进行更改,出现的现象就是可能会导致最终get()结果不符合我们的预期。最终调整方案就是在每个任务supplyAsync()之前单独赋予一个新的final对象只为此任务使用,不再进行共用。

CompletableFuture简介

CompletableFuture是Java8引入的一个类,用于在异步编程中处理多个任务。它可以将任务链接起来,使得一个任务的结果可以作为另一个任务的输入。CompletableFuture提供了丰富的方法来处理任务的结果,例如处理异常、合并多个任务的结果等。

CompletableFuture可以通过工厂方法创建,例如CompletableFuture.supplyAsync()用于执行一个有返回值的异步任务,CompletableFuture.runAsync()用于执行一个没有返回值的异步任务。

CompletableFuture的线程安全问题
虽然CompletableFuture提供了强大的功能,但在多线程环境中使用时,需要注意其线程安全问题。

1. 共享变量引发的问题

如果多个任务共享一个变量,并且对该变量进行修改操作,可能会导致不确定的结果。例如下面的代码:

public class CompletableFutureDemo {
    private static int count = 0;

    public static void main(String[] args) {
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            for (int i = 0; i < 1000; i++) {
                count++;
            }
        });

        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
            for (int i = 0; i < 1000; i++) {
                count++;
            }
        });

        CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2);
        combinedFuture.join();

        System.out.println("Count: " + count);
    }
}

在上面的代码中,两个任务分别对count变量进行1000次增加操作。由于count变量是共享的,这个操作并不是线程安全的。当两个任务交替执行时,可能会导致count的值不是预期的2000。

2. 竞态条件

CompletableFuture的方法可以通过多个线程同时调用,这可能导致竞态条件。例如,以下代码中的thenApply()方法会在前一个任务完成后执行,返回一个新的CompletableFuture对象。

public class CompletableFutureDemo {
    private static int count = 0;

    public static void main(String[] args) {
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            return 1;
        });

        CompletableFuture<Integer> future2 = future1.thenApply((result) -> {
            return result + 2;
        });

        CompletableFuture<Integer> future3 = future1.thenApply((result) -> {
            return result * 2;
        });

        CompletableFuture<Integer> combinedFuture = CompletableFuture.allOf(future2, future3);
        combinedFuture.join();

        System.out.println("Future2 result: " + future2.get());
        System.out.println("Future3 result: " + future3.get());
    }
}

在上面的代码中,future2和future3都依赖于future1的结果,但它们之间并没有明确的顺序关系。如果多个线程同时调用thenApply方法,可能会导致future2和future3的结果不一致,因为它们有可能使用了不同的future1结果。 

解决CompletableFuture的线程安全问题

为了解决CompletableFuture的线程安全问题,可以采取以下措施:

避免共享变量:在多个任务之间尽量避免共享变量,使用局部变量或者将变量作为方法参数传递。
使用线程安全的数据结构:如果必须共享变量,可以使用线程安全的数据结构,例如AtomicInteger代替int。
使用同步机制:可以使用synchronized关键字或者Lock接口来保证多个任务的互斥访问。

本篇文章如有帮助到您,请给「翎野君」点个赞,感谢您的支持。

首发链接:https://www.cnblogs.com/lingyejun/p/18293069

posted @ 2024-07-10 06:53  翎野君  阅读(9)  评论(0编辑  收藏  举报