我有一个方法,许多线程并行访问,该方法使用一个具有两个我无法控制的同步方法的类。 getObject和createNewObject。我想确保我不创建多个对象(MyObject)。
MyObject obj;
public void method1() {
obj = getObject("key");
if (obj == null)
obj = createNewObject("key");
}
我认为,这不会起作用,因为线程可以在获取和创建方法之间暂停,因此另一个线程也可以进入并创建一个对象。同步的createNewObject方法通过检查一个名为“key”的对象是否已存在并在这种情况下抛出异常来解决此问题。
以下哪种方法会受到优先考虑?性能,安全性和设计明智。我听说双锁型(方法3)不起作用?也许我应该使用method1?
大多数时候,会找到该对象,所以没有问题。在极少数情况下跳过同步并处理异常可能会有更好的性能吗?
MyObject obj;
public synchronized void method1() {
obj = getObject("key");
if (obj == null)
obj = createNewObject("key");
}
public void method2() {
obj = getObject("key");
if (obj == null)
try {
obj = createNewObject("key");
} catch (Exception e) { // ops, someone already created object "key"
obj = getObject();
}
}
public void method3() {
obj = getObject("key");
if (obj == null)
obj = getObj("key");
}
public synchronized MyObject getObj(String key) {
MyObject obj = getObject(key);
if (obj == null)
obj = createNewObject(key);
return obj;
}
答案 0 :(得分:8)
开始使用method1
,直到探查器告诉您它是瓶颈。这是最干净的实现,你知道它会一直正常工作。如果以后你看到的数据表明你在连续的电话中浪费了很多时间,那么你可以考虑尝试别的东西。
答案 1 :(得分:4)
这需要一些测试和分析,但我很确定你不会通过使用任何技巧获得任何重要的性能,因为在任何情况下都会执行同步,因为你每次调用getObject()方法,这是同步。所以这不是“同步/不同步”的那种差异,而是“同步/双同步”,这应该不是那么多。如果你正在进行同步,那么最好完全做到这一点。这意味着你的案例中的method1()。
<强>更新强>
虽然method2()看起来也很有希望,但我刚刚意识到它有问题:因为它没有将写入同步到obj
字段,所以其他线程可能看不到它的更新值。因此,如果除了调用method2()的线程之外的其他线程访问obj
字段,则method2()不正确。
如果你将obj
字段设置为volatile,我相信它可能会起作用(尽管不是100%肯定),因为getObject()是同步的,因此不存在“对非易失性对象的易失性引用”问题。在getObject()返回后,它会执行写入屏障,因此可以保证在主内存中存在完全初始化的对象。并且由于没有线程具有该对象的本地缓存副本,因此任何线程都可以访问obj
字段。除非obj
字段引用的对象是可变的,否则无论如何都应该同步对它的所有访问。
答案 2 :(得分:3)
现代虚拟机中的同步消耗的资源/执行时间非常少。我只是简单地同步检查/创建方法。过早优化会花费你很多时间/心痛,如果成为问题,你最好不要担心这类事情。
答案 3 :(得分:0)
编辑:下面我写了双重检查锁定ideom,但它有一些重大缺陷,在
中有描述----原帖如下----
最好的解决方案是:
Object obj;
public Object getObject() {
if( obj == null ) {
synchronized(this) { // or on something else
if( obj == null ) {
obj = createObject();
}
}
}
return obj;
}
private Object createObject() {
...
}
它的优点是同步仅在对象的关键创建阶段发生,但仍然有效。
答案 4 :(得分:0)
你说你无法控制createNewObject,所以在这种情况下,method1是正确的答案,我只是赞成了这样说的人。但听起来像createNewObject的设计很糟糕。如果它要检查对象是否已经存在,那么在这种情况下它应该返回该对象而不是抛出异常。要求调用者检查对象是否存在,以及是否调用函数然后重复检查对象是否存在,这是愚蠢的。
答案 5 :(得分:0)
在阅读了一下之后,我相信一个真正最好的答案是使用Initialize-On-Demand Holder Class习语:
private static class LazySomethingHolder {
public static Something something = new Something();
}
public static Something getInstance() {
return LazySomethingHolder.something;
}
它没有并发问题,即使是公共路径上的volatile变量也没有锁定。