V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
golangLover
V2EX  ›  Java

想请问大家关于 completablefuture 中参数传递线程安全问题。

  •  
  •   golangLover · 2021-10-22 22:25:41 +08:00 · 1521 次点击
    这是一个创建于 1152 天前的主题,其中的信息可能已经有所发展或是发生改变。

    搜了 stackoverflow 似乎没有人提过这个问题,因为我也是迫于公司要初学 java,大家见笑了。

    这段代码主要想大家帮忙看看 iterate 函数在 for 循环会不会有线程安全的问题。

    主要的问题是:

    不肯定 iterate 函数 在 for 循环之中,intA 以及 uuid 会不会有线程安全的问题。 1: 就是 thenApply 里面的 intA 与 supplyAsync 里面的是否一致。 2: 也不肯定这传入的参数 intA 与 uuid, 与 thenApply 里面拿到的会不会都是同一组参数。也就是会不会因为循环而导致 uuid 是拿到较为新的情况,而 intA 比较旧?导致计算结果不合理。

    我原本想把结果 print 出来测试一下,但是又无从下手,因为这如果真的有线程安全问题也不是 debugger 能看的出来。

    先感谢大家的赐教。谢谢

    import java.util.ArrayList;
    import java.util.List;
    import java.util.UUID;
    import java.util.concurrent.CompletableFuture;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    
    public class Test {
        public static void main(String[] args) {
            new Test().run();
        }
    
        private void run() {
            List<Integer> list = IntStream.range(1,10000).boxed().collect(Collectors.toList());
    
            List<CompletableFuture<Integer>> cfList = new ArrayList<>();
    
            for (Integer intA: list) {
                String uuid = UUID.randomUUID().toString();
                CompletableFuture<Integer> future = this.iterate(intA,uuid);
                cfList.add(future);
            }
    
            CompletableFuture<List<Integer>> resultCf = this.allOf(cfList);
            resultCf.join();
        }
    
        private  CompletableFuture<Integer> iterate(Integer intA, String uuid) {
    
            return CompletableFuture.supplyAsync(()->{
    //            假设需要用到第一个参数,然后返回
                return intA+2;
            }).thenApply((req)->{
                return intA+5;
            }).thenApply((value)->{
    //            假设这个耗时操作要用到第一个参数以及第二个参数。
    //            不肯定 在 for 循环之中,intA 以及 uuid 会不会有线程安全的问题。
    //            1: 就是 thenApply 里面的 intA 与 supplyAsync 里面的是否一致。
    //            2: 也不肯定这传入的参数 intA 与 uuid, 与 thenApply 里面拿到的会不会都是同一组参数。
    //            也就是会不会因为循环而导致 uuid 是拿到较为新的情况,而 intA 比较旧?导致计算结果不合理。
                System.out.println(uuid);
                return 3+value;
            });
        }
    
    //     等待 list 完毕
        private  <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futuresList) {
            CompletableFuture<Void> allFuturesResult =
                    CompletableFuture.allOf(futuresList.toArray(new CompletableFuture[0]));
            return allFuturesResult.thenApply(v ->
                    futuresList.stream().
                            map(CompletableFuture::join).
                            collect(Collectors.toList())
            );
        }
    }
    
    8 条回复    2021-10-25 22:18:29 +08:00
    hingbong
        1
    hingbong  
       2021-10-22 22:45:34 +08:00
    lambda 里面拿外面的变量都是栈里面而且还是 final 的吧,应该没问题
    0Vincent0Zhang0
        2
    0Vincent0Zhang0  
       2021-10-22 23:36:44 +08:00 via Android
    这个场景下,没有线程安全问题。
    因为:
    1.iterate 方法里没有“修改”intA (给 intA 赋值不算“修改”)
    2.list 里面没有相同的 Integer
    chendy
        3
    chendy  
       2021-10-22 23:42:28 +08:00
    貌似全程没看到会被多个线程访问到的变量,所以是不存在线程安全问题
    thenApply 是上一步执行完了执行下一步,是同步的,所以最后一个 thenApply 里能达到的 value 就是最新的
    另外不存在 intA 比较旧的问题,intA 全程没有被重新赋值过,一直就是初始值
    wangyu17455
        4
    wangyu17455  
       2021-10-23 16:03:58 +08:00
    lambda 里面访问外部的临时变量,是通过构造函数传参实现的,这也是为什么不能在 lamda 和匿名内部类里面修改外部临时变量的值,两者只通过运行构造函数同步一次。所以在 computablefuture 里面虽然运行顺序由你的代码指定,但是你传入的三个 lambda 是在同一次循环内部创建完成的。
    golangLover
        5
    golangLover  
    OP
       2021-10-24 19:14:36 +08:00
    @hingbong @0Vincent0Zhang0 @chendy @wangyu17455 感谢各位大佬的赐教
    golangLover
        6
    golangLover  
    OP
       2021-10-24 19:16:15 +08:00
    @wangyu17455 另外想请问这个 “lambda 只通过运行构造函数同步一次” 和 “你传入的三个 lambda 是在同一次循环内部创建完成的” 这里的出处能在哪里找到。我觉得自己对这个了解不是太深入。搜了 lambda 串联好像没提到这个。谢谢
    wangyu17455
        7
    wangyu17455  
       2021-10-25 13:26:55 +08:00
    “只通过构造函数同步一次“反编译就可以看到, “你传入的三个 lambda 是在同一次循环内部创建完成的” 把 lambda 当做匿名内部类理解就行,匿名内部类实际上就是正常类的语法糖,()->{xxx}在编译的时候就被当做 new X(y)处理
    golangLover
        8
    golangLover  
    OP
       2021-10-25 22:18:29 +08:00
    @wangyu17455 好的,谢谢你!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3044 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 14:01 · PVG 22:01 · LAX 06:01 · JFK 09:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.