如何以线程安全的方式遍历(系统)属性?

How can I iterate over (system) properties in a thread safe manner?

以下片段开始在我的应用程序(在迭代器中)中抛出 ConcurrentModificationException

final Map<?, ?> systemProperties = System.getProperties()
final Map<String, String> properties = (Map<String, String>) systemProperties

for (final Entry<String, String> entry : properties.entrySet()) { // exception here
    System.out.println(entry)
}

我是运行一个多线程应用程序,不幸的是我无法访问修改系统属性的代码(它甚至可能是第三方库)。

要解决这个问题,我们可以拍摄系统 属性 键的 快照

final Properties systemProperties = System.getProperties()
final Set<String> keys = systemProperties.stringPropertyNames()

for (final String key : keys) {
    System.out.println("key: " + key)
    final String value = systemProperties.getProperty(key)
    System.out.println("value: " + value) // value can be null!
}

注意 value 评论 - 虽然 stringPropertyNames 声明 set of keys in this property list where the key and its corresponding value are strings,但系统 属性 可能已同时更改。


为什么跑这么多路?

系统属性是 java.util.Properties 的实例,其方法 getPropertysetProperty 是线程安全的。

不幸的是,Properties 的条目集的迭代器(我曾在问题中使用过)不是线程安全的:

If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation, or through the setValue operation on a map entry returned by the iterator) the results of the iteration are undefined

所以实际上当我迭代那个地图时,一些系统 属性 被修改了(= 那个条目说被修改了),这导致 CME 被抛出。


这个问答对也适用于任何通用的 Properties 用法 - 只是系统属性让它变得更棘手,能够直接使用诸如 java.lang.System.setProperty(String, String) 之类的静态访问它们 - 所以控制所有访问(特别是在共享代码)变得更难。

您可以将您的属性包装在 ConcurrentHashMap 中,这样您的任何复合操作(例如迭代、导航、检查并执行等)都将是线程安全的。 例如

ConcurrentHashMap<String, String> props = new ConcurrentHashMap<>(
    (Map<String, String>)(Object)System.getProperties());

for(Map.Entry<String, String> entry: props.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

注意ConcurrentHashMap返回的迭代器是周一致,这意味着它可能反映也可能不反映迭代器构造后集合的变化.如果这不是您想要的,您可以改用 Collections.synchronizedMap(),这会在并发性方面造成一些损失。