Java 11 Properties类是否采用双重同步?为何用ConcurrentHashMap仍加同步方法
我正在深入研究Java 11 Properties类的实现。在Java 11源码中,Properties类将数据存储在名为map的ConcurrentHashMap字段中。调用Properties的setProperty方法时,底层会调用内部ConcurrentHashMap的put方法。我知道ConcurrentHashMap是线程安全的集合,因此不理解为何setProperty方法还要加synchronized修饰,毕竟ConcurrentHashMap本身已具备同步能力。这种同步似乎会削弱ConcurrentHashMap的优势,意味着Properties类的用户需要等待方法锁,而该方法内部已经使用了自带同步机制的并发集合。
public class Properties extends Hashtable<Object,Object> { /** * use serialVersionUID from JDK 1.1.X for interoperability */ private static final long serialVersionUID = 4112578634029874840L; private static final Unsafe UNSAFE = Unsafe.getUnsafe(); /** * A property list that contains default values for any keys not * found in this property list. * * @serial */ protected volatile Properties defaults; /** * Properties does not store values in its inherited Hashtable, but instead * in an internal ConcurrentHashMap. Synchronization is omitted from * simple read operations. Writes and bulk operations remain synchronized, * as in Hashtable. */ private transient volatile ConcurrentHashMap<Object, Object> map; public synchronized Object setProperty(String key, String value) { return put(key, value); } @Override public synchronized Object put(Object key, Object value) { return map.put(key, value); }
原因解析
向后兼容性要求:Properties继承自Hashtable,而Hashtable的所有方法都带有
synchronized修饰。为了保证老代码的兼容性,Properties必须维持与Hashtable一致的同步语义——比如有些代码依赖Hashtable的方法级锁来实现“检查-修改”这类复合操作,Properties不能破坏这种约定,否则会导致老代码出现并发问题。复合操作的原子性保障:ConcurrentHashMap仅能保证单个操作的线程安全,但Properties存在大量复合操作(比如
load、store批量读写,或者自定义的先查询再修改逻辑)。synchronized方法锁可以确保这些跨多个步骤的操作是原子性的,避免在操作过程中被其他线程干扰,导致数据不一致。与defaults字段的协同同步:Properties的
defaults字段用于存储默认属性,读取属性时会先检查当前map,再遍历defaults链。如果修改操作不加全局锁,可能会和读取操作、defaults的修改操作产生并发冲突,导致读取到半更新的状态。
源码注释也明确说明:"Writes and bulk operations remain synchronized, as in Hashtable",这是有意设计的——既利用ConcurrentHashMap优化简单读操作的性能,又通过方法级锁维持Hashtable的同步行为,兼顾兼容性与部分场景的性能提升。
内容的提问来源于stack exchange,提问作者Jesus Vasquez

