Java 抛出异常vs同步

Java 抛出异常vs同步,java,exception,synchronized,Java,Exception,Synchronized,我有一个多线程并行访问的方法,它使用一个类和两个我无法控制的同步方法。getObject和createNewObject。我希望确保我没有创建多个对象(MyObject) 我认为,这是行不通的,因为线程可以挂起在get和creating方法之间,这样另一个线程也可以进来创建一个对象。synchronized createNewObject方法通过检查名为“key”的对象是否已存在并在这种情况下引发异常来修复此问题 以下哪种方法是首选的?性能、安全性和设计方面。我听说双重锁定类型(方法3)不起作用

我有一个多线程并行访问的方法,它使用一个类和两个我无法控制的同步方法。getObject和createNewObject。我希望确保我没有创建多个对象(MyObject)

我认为,这是行不通的,因为线程可以挂起在get和creating方法之间,这样另一个线程也可以进来创建一个对象。synchronized createNewObject方法通过检查名为“key”的对象是否已存在并在这种情况下引发异常来修复此问题

以下哪种方法是首选的?性能、安全性和设计方面。我听说双重锁定类型(方法3)不起作用?也许我应该用方法1

大多数情况下,会找到对象,因此没有问题。在这些罕见的情况下,跳过同步并处理异常可能会提高性能

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;
 }

现代虚拟机中的同步只消耗很少的资源/执行时间。我只需围绕检查/创建方法进行同步。过早的优化会花费你大量的时间/心痛,如果出现问题,你最好还是担心这类事情。

开始使用
method1
,直到探查器告诉你这是一个瓶颈。这是最干净的实现,您知道它将一直正常工作。如果以后您看到数据显示您在连续调用中浪费了大量时间,那么您可以考虑尝试其他方法。

这需要一些测试和分析,但我相当肯定,使用任何技巧都不会获得任何显著的性能,因为同步在任何情况下都会执行,就像您调用getObject()一样方法,这是同步的。所以这不是“同步/不同步”的区别,而是“同步/双重同步”,不应该太多。如果您仍在同步,最好尽可能地进行同步。在您的例子中,这意味着method1()

更新

虽然method2()看起来也很有希望,但我刚刚意识到它有一个问题:因为它不同步写入
obj
字段,其他线程可能看不到它的更新值。因此,如果调用method2()的线程之外的其他线程访问了
obj
字段,则method2()是不正确的

如果将
obj
字段设置为volatile,我相信它可能会工作(但不是100%确定),因为getObject()是同步的,所以不应该存在“对非volatile对象的volatile引用”问题。在getObject()返回后,它执行写屏障,因此保证在主内存中存在完全初始化的对象。由于没有线程具有该对象的本地缓存副本,因此任何线程都可以访问
obj
字段。除非
obj
字段引用的对象是可变的,否则在这种情况下,对它的所有访问都应该同步


不过,这仍然没有多大意义。完全不同步的读取访问仍然是不可能的,因此干净的实现仍然比“智能”实现要好。

编辑:下面我编写了双重检查锁定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() {
    ...
} 

它的优点是,同步只发生在对象的关键创建阶段,但仍然有效

你说你对createNewObject没有控制权,所以在这种情况下,method1是正确的答案,而我只是投票给了这么说的人。但我觉得createNewObject的设计很糟糕。如果它要检查对象是否已经存在,那么在这种情况下,它应该返回该对象,而不是抛出异常。要求调用者检查对象是否存在是愚蠢的,如果它不调用一个函数,然后重复检查对象是否存在。

读了一点之后,我相信最好的答案是使用Initialize On Demand Holder类习惯用法:

private static class LazySomethingHolder {
    public static Something something = new Something();
}

public static Something getInstance() {
    return LazySomethingHolder.something;
}

它没有并发问题,甚至对公共路径上的易失性变量也没有锁定。

查看method3无法工作的详细信息。这与我的method3真的不一样吗?我听说这个双重锁定装置有点不好。是的。我赞成。但是我仍然需要阅读您的问题评论中发布的IBM文章,我可能会包含更多的知识。我认为,由于第二个检查是针对最终位置的,而不是存储正在构造的对象的temp变量,所以它可能是正确的。这只是猜测,我可以看到一个编译器试图优化一个函数,该函数只是
将new Object()
返回为与
obj=new Object()
相同的值。我回到我的答案上来,因为我不希望有依赖于实现细节的代码。这是双重检查锁定,并且被破坏了。是的,你是对的。这个坏了,我错了。它必须稍微修改以使用易失性对象。。。但是,似乎应该选择Initialize On Demand Holder类习惯用法。而且,在现代JVM中,重新获得一个已经拥有的互斥锁非常快,因此方法1绝对是首选,并且很可能不会有任何不同,而最初的“过早优化是所有邪恶的根源”是:方法2。但是createNewObject metod是同步的,如果您试图创建一个键已经存在的对象,就会抛出一个异常。因此,唯一可能发生的事情是,另一个线程在有人写入obj字段之前看到该字段为null。因此线程尝试创建对象本身,但收到异常,因为另一个线程刚刚创建了该对象。
private static class LazySomethingHolder {
    public static Something something = new Something();
}

public static Something getInstance() {
    return LazySomethingHolder.something;
}