在循环setProperety之前,我应该首先获得对属性的锁定吗?

时间:2016-02-01 21:15:55

标签: java performance properties thread-safety synchronized

java class Properties是一个线程安全的类,根据它的文档:

  

此类是线程安全的:多个线程可以共享单个Properties对象,而无需外部同步。

由于这个原因,我习惯在HashMap Map的{​​{1}}实现中维护我的属性,这不是线程安全的,但更轻量级。更具体地说,实现线程安全需要额外的锁定机制,这将对性能产生影响。我可以通过简单地在静态类初始化器中隔离我的属性初始化来轻松避免这种情况,这将保证它在我在同一类的实例方法中使用任何get调用之前完成。

到目前为止这只是一个叙述,引导我到实际的问题。最终我需要恢复只能接受“属性”的API。作为参数,一个例子是DriverManager.getConnection(String,Properties)。我需要将Map转换为Properties

所以第一次尝试看起来像这样:

  Properties properties = new Properties();
  this.propertyMap.forEach((k,v)->{properties.setProperty(k, v);});
  connection = DriverManager.getConnection(url, properties);

明显的问题,或者可能不那么明显,因为我避免使用实际的for循环,是我使用重复调用Properties.setProperty。如果Properties真的是线程安全的,那么这必须意味着每次调用setProperty都是同步的并且添加了单独的锁定/解锁机制。

在这种情况下,我首先手动锁定整个Properties实例会不会更好?

Connection connection;
Properties properties = new Properties();
synchronized (properties) {
  // Properties is implemented as thread-safe. As it adds
  // additional thread locking, it's use is localized to just
  // here to avoid consequential performance issues. We will
  // do a last minute conversion from Map to Properties right here.
  this.propertyMap.forEach((k,v)->{properties.setProperty(k, v);});
  connection = DriverManager.getConnection(url, properties);
}

我可能期待的一个问题是手动锁定属性以及对setProperties的单独调用都可能导致死锁,但它似乎运行正常。

1 个答案:

答案 0 :(得分:0)

我根据不正确的基准测试得出了无效的结论。即使我尝试过某种类型的热身,但这还不够。

只有通过第三次迭代(一旦添加了迭代),使用完整数据集,测量结果就会消失,并且在彼此的范围内,所以它们可以被称为相同而且没有显着的差异。

修订结论

结论现在这些方法实际上几乎无关紧要。我仍然可以看到HashMap总体上比Properties表现更好,但还不足以让我选择性能而不是可读性。

根据@Holger的建议,我建议只使用Properties.putAll(other)方法。它很快,并且与其他人一样表现良好。

但是,如果您在多线程环境中使用它,我建议使用HashMap,并仔细考虑并发访问。通常,这很容易实现,而不需要过度锁定/同步。

我认为这是一个很好的教训。

以下是更加标准化的测量。我通过目测和挑选没有异常值的样本而不是试图在多个样本上进行平均/中值来作弊。只是为了避免这样做的复杂性(和额外的工作),或者必须获得外部工具。

+----------------------------------+----------+--------+
|               Name               | Unlocked | Locked |
+----------------------------------+----------+--------+
| putAll->Properties               | 1,644    | 1,429  |
| forEach->setProperty->Properties | 1,474    | 1,740  |
| stream->setProperty->Properties  | 1,484    | 1,735  |
| loop->setProperty->Properties    | 2,022    | 1,606  |
| forEach->put->Properties         | 1,590    | 1,411  |
| stream->put->Properties          | 1,538    | 1,514  |
| loop->put->Properties            | 1,380    | 1,666  |
| putAll->Map                      | 1,380    | 509    |
| forEach->put->Map                | 935      | 915    |
| stream->put->Map                 | 927      | 888    |
| loop->put->Map                   | 880      | 1,015  |
+----------------------------------+----------+--------+

方法

测试方法与上表中的顺序相同,pPropertiesmMap为目标:

  p.putAll(SOURCEMAP);
  SOURCEMAP.forEach(p::setProperty);
  SOURCEMAP.entrySet().stream().forEach((e)->{p.setProperty(e.getKey(), e.getValue());});
  for (Map.Entry<String,String> e:SOURCEMAP.entrySet()) p.setProperty(e.getKey(), e.getValue());
  SOURCEMAP.forEach(p::put);
  SOURCEMAP.entrySet().stream().forEach((e)->{p.put(e.getKey(), e.getValue());});
  for (Map.Entry<String,String> e:SOURCEMAP.entrySet()) p.put(e.getKey(), e.getValue());
  m.putAll(SOURCEMAP);
  SOURCEMAP.forEach(m::put);
  SOURCEMAP.entrySet().stream().forEach((e)->{m.put(e.getKey(), e.getValue());});
  for (Map.Entry<String,String> e:SOURCEMAP.entrySet()) m.put(e.getKey(), e.getValue());

GitHub上提供了完整的源代码:http://github.com/jdesmet/properties-benchmarking