多线程更新同一对象不同属性是否安全?CompletableFuture方案解析
多线程更新同一对象不同属性的线程安全问题分析
一、Thread实现方案的安全性分析
先看你的Thread实现代码:
public Book enrichBook(Book book) { Long bookId = book.getId(); Thread t1 = new Thread(() -> book.setAuthors(authorService.findByBookId(bookId))); Thread t2 = new Thread(() -> book.setPrice(priceService.calcByBookId(bookId))); t1.start(); t2.start(); t1.join(); t2.join(); return book; }
核心结论:不会出现仅保留单个属性更新的情况,当前实现是安全的
- 两个线程分别修改
Book对象的不同独立属性,setAuthors和setPrice操作的是完全独立的字段,不存在写覆盖的竞态条件——线程t1的写入不会影响t2的写入,反之亦然。 - 你调用了
t1.join()和t2.join(),根据Java内存模型的语义,当主线程等待线程终止后,该线程对共享对象的所有修改都会被主线程可见。因此主线程最终返回的Book对象,两个属性的更新都会被正确保留。 - 额外注意:如果
authors是线程不安全的集合(比如普通ArrayList),后续对这个集合的并发操作可能有问题,但仅当前的setAuthors赋值操作是原子的,不存在风险。
二、CompletableFuture实现方案的可行性分析
你的CompletableFuture实现代码:
public Book enrichBook(Book book) { Long bookId = book.getId(); CompletableFuture<Void> authorFuture = CompletableFuture.runAsync(()-> book.setAuthors(authorService.findByBookId(bookId)), appConfig.getAsyncExecutor()); CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(()-> book.setPrice(priceService.calcByBookId(bookId)), appConfig.getAsyncExecutor()); List<CompletableFuture<Void>> completableFutures = new ArrayList<>(); completableFutures.add(authorFuture); completableFutures.add(priceFuture); CompletableFuture<Void> allFuturesResult = CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[completableFutures.size()])); allFuturesResult.join(); // try - catch is igored here return book; }
核心结论:方案可行,正常情况下不会出现预期外结果
- 这个方案的本质和Thread实现逻辑一致:异步执行两个独立的属性赋值任务,等待所有任务完成后返回结果。
CompletableFuture.allOf()会等待所有异步任务终止,join()同样保证任务的修改对主线程可见。 - 同样因为修改的是不同属性,不存在竞态条件,两个属性的更新都会被正确保留。
- 需要注意的是:你忽略了异常处理,实际生产环境中,
join()会抛出CompletionException(封装了异步任务中的异常),如果authorService或priceService执行失败,会导致当前方法抛出异常,建议添加try-catch块捕获并处理异常,避免影响调用方。
内容的提问来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

