如何用线程安全对象初始化HashTable?存非线程安全ArrayList是否影响其线程安全?
关于Hashtable线程安全与嵌套ArrayList的问题解答
1. 如何线程安全地初始化Hashtable?
分两种场景来看:
- 如果是单线程环境或局部变量:直接
new Hashtable<>()就足够了,完全不需要额外操作。因为此时新实例还未被其他线程共享,不存在竞态条件。 - 如果是多线程共享的全局/静态实例且需要懒加载:得保证初始化过程的线程安全,常用两种省心的方式:
- 静态初始化块:利用JVM类加载的天然线程安全性,在类加载阶段完成初始化,示例代码:
public class Dealership { private static final Hashtable<Employee, ArrayList<Car>> carDealership; static { carDealership = new Hashtable<>(); } } - 双重检查锁定:记得给变量加
volatile修饰符,防止JVM指令重排序导致的半初始化问题:public class Dealership { private static volatile Hashtable<Employee, ArrayList<Car>> carDealership; public static Hashtable<Employee, ArrayList<Car>> getCarDealership() { if (carDealership == null) { synchronized (Dealership.class) { if (carDealership == null) { carDealership = new Hashtable<>(); } } } return carDealership; } }
- 静态初始化块:利用JVM类加载的天然线程安全性,在类加载阶段完成初始化,示例代码:
如果不需要懒加载,静态初始化块是最省心的选择,不用自己写复杂的同步逻辑。
2. 用非线程安全的ArrayList作为Hashtable的值,会损害整体线程安全性吗?
那肯定会!Hashtable的线程安全只覆盖它自身的方法(比如put()、get()),保证内部哈希桶结构不会被多线程乱改。但当你从Hashtable中取出ArrayList后,Hashtable的同步机制就彻底管不到这个列表了。
举个实际场景:线程A刚拿到某个员工的车辆列表准备添加新车,线程B同时拿到同一个列表要删除车辆——这两个操作都不在Hashtable的同步保护范围内,分分钟出现ConcurrentModificationException,或者元素丢失、重复的情况,直接破坏整体线程安全。
3. 将ArrayList的所有操作包裹在同步代码块中,但不声明为同步列表,这个方案可行吗?
这个方案是可行的,但有几个关键规则必须严格遵守,不然很容易翻车:
- 锁对象必须统一:所有操作同一个ArrayList的代码,都要在同一个锁的保护下,有两种选择:
- 用Hashtable实例当锁:每次读写ArrayList时,都把代码包在
synchronized(carDealership)块里,包括从Hashtable获取ArrayList的操作(防止拿取时其他线程修改列表引用):// 正确添加车辆的写法 synchronized (carDealership) { ArrayList<Car> cars = carDealership.get(employee); if (cars == null) { cars = new ArrayList<>(); carDealership.put(employee, cars); } cars.add(new Car()); } // 正确删除车辆的写法 synchronized (carDealership) { ArrayList<Car> cars = carDealership.get(employee); if (cars != null) { cars.remove(carToRemove); } } - 用ArrayList自身当锁:这种方式粒度更细,但拿取ArrayList时必须先加Hashtable的锁,拿到后再锁定列表操作,否则可能拿到半初始化的列表。新手不推荐这种方式,容易出错。
- 用Hashtable实例当锁:每次读写ArrayList时,都把代码包在
- 绝对不能在同步块外持有ArrayList的引用:比如把ArrayList赋值给全局变量后在同步块外操作,这会直接绕过同步机制,线程安全问题照样出现。
- 对比
Collections.synchronizedList:手动同步和用Collections.synchronizedList(new ArrayList<>())本质逻辑一致(后者内部也是用同步块实现),但synchronizedList已经封装好所有操作,不容易遗漏同步逻辑。如果操作不复杂,建议直接用它,省得手动写同步出问题。
内容的提问来源于stack exchange,提问作者Catalin Ghita
相关产品推荐
相关产品推荐

