Lombok @Singular注解如何实现线程安全的Builder?
Lombok 原生没有提供直接的配置开关,可一键生成支持线程安全的@Singular Builder。默认实现下@Singular注解为List类型字段生成的Builder存储容器是普通ArrayList,多线程并发调用单元素添加方法时,确实会出现元素缺失、状态不一致、数组下标越界等并发问题。
以下是两种低冗余的可行实现方案,不需要完全放弃@Singular注解手写全量Builder逻辑:
方案1:覆写Builder内的集合初始化逻辑,保留全部@Singular原生能力
你只需要在实体类中写对应Builder静态内部类,手动定义对应集合字段为线程安全容器即可,Lombok检测到该字段已存在时,不会重复生成字段定义,其余单元素添加、批量添加、build()、clear()等方法仍会由@Singular自动生成,所有操作都会作用在你定义的线程安全容器上,样板代码量极低:import java.util.ArrayList; import java.util.Collections; import java.util.List; import lombok.Builder; import lombok.Singular; @Builder public class Avengers { @Singular private List<String> members; // 仅手动定义集合字段,其余Builder逻辑由Lombok自动生成 public static class AvengersBuilder { private List<String> members = Collections.synchronizedList(new ArrayList<>()); } }如果是Set、Map类型的@Singular字段,对应替换为
Collections.synchronizedSet()、Collections.synchronizedMap()包装的线程安全容器即可。方案2:并发场景下先独立收集元素,再一次性传入Builder
如果不方便修改实体类的Builder定义,可以避免多线程直接操作同一个Builder实例,先在线程任务中把元素添加到线程安全的收集容器中,等待所有任务执行完成后,调用@Singular生成的批量添加方法一次性传入所有元素:import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; // 初始化线程安全的收集容器 final List<String> memberCollector = Collections.synchronizedList(new ArrayList<>()); ExecutorService executorService = Executors.newFixedThreadPool(3); executorService.submit(() -> memberCollector.add("Iron Man")); executorService.submit(() -> memberCollector.add("Thor")); executorService.submit(() -> memberCollector.add("Captain America")); // 等待所有任务执行完成 executorService.shutdown(); executorService.awaitTermination(1, TimeUnit.MINUTES); // 一次性传入所有元素构建实例 final Avengers team = Avengers.builder() .members(memberCollector) .build();该方案完全不需要调整实体类的Lombok配置,线程安全逻辑收敛在业务组装侧,适配无法修改实体类定义的场景。
两种方案都可以保留@Singular的核心特性:build()生成的最终集合是不可变的、支持单元素/批量添加语义、支持Builder状态重置,相比全手写Builder可以减少90%以上的冗余样板代码。
内容的提问来源于stack exchange,提问作者Krease

