【问题标题】:Ensuring "singleton-like" uniqueness of non-final field, where to lock?确保非final字段的“单例”唯一性,在哪里锁定?
【发布时间】:2012-06-09 19:23:14
【问题描述】:

我希望 JdbcPersistenceManager 始终只有一个 jdbcConnection 并通过以下方式对其进行实例化:JdbcConnectionManager.getJdbcConnection()

这是一个简单的模式,摘自 (2004-11-01 | Head First Design Patterns | O'Reilly Media | 688p | by Elisabeth Freeman | ISBN-0596007124),可能被误用、误解和不合适。

是否应该在“this”或专门用于跟踪锁的特定新(静态?)字段上进行同步()锁定?我该怎么做?

public class JdbcPersistenceManager implements PersistenceManager {
  private volatile Connection jdbcConnection;
  /* ... */
  private Connection getJdbcConnection() throws JdbcConnectionFailureException {
    if (jdbcConnection == null) {
      synchronized (this) {
        if (jdbcConnection == null) {
          jdbcConnection =
              JdbcConnectionManager.getJdbcConnection(jdbcConnectionParameters);
        }
      }
    }
    // label:here
    return jdbcConnection;
  }
}

假设 jdbcConnection 的实例化,即使我们想要标记为“label:here”的点,只是为了论证,如何最好地检查连接是否仍然有效,如果不是则重新创建它?

ConnectionPooling 不是我想在这里做的。只有一个连接...如果它是“null”或“invalid”,则重新生成/重新打开它。

编辑:

我希望 getJdbcConnection 做的是:

1) 提供 jdbcConnections,确保在任何给定时间只存在其中一个(不应允许客户端保留对 2 个不同连接的引用)

和

2) 重新生成(即重新调用 JdbcConnectionManager.getJdbcConnection())“private volatile Connection jdbcConnection”字段,如果由于某种原因它被关闭了

(例如,客户端 1 出现,获得连接但 关闭,客户​​端 2 出现,连接 not null 但无法使用,因此她得到一个再生的)。

注意:我意识到没有什么可以阻止客户端 1 获得连接,而客户端 2 获得相同的连接,就像设计上一样,并且在客户端 1 通过他的参考关闭它后仅一毫秒使用它...我不知道如果这完全可以解决?

【问题讨论】:

  • 您可以为每个客户端创建一个包装器/代理 Connection 实例,它们都包装相同的真实 Connection 并且不传播 close() 方法,但如果它已关闭,则设置内部真实Connection 到 null/invalid 并在尝试进一步使用时抛出异常。
  • 拥有一个包装类似乎是个好主意......“每个问题都可以通过添加另一个间接级别来解决”,实际报价如何?你提出问题的建议破坏了我想要的再生能力……不是吗?
  • 我希望每个客户端都这样,所以如果客户端调用close(),任何后续操作都会抛出异常,就像真正的连接一样,但这只是一个细节。

标签: java multithreading locking singleton synchronized


【解决方案1】:

双重检查锁定应该在类而不是this上进行:

if (jdbcConnection == null) {
  synchronized (JdbcPersistenceManager.class) {
    if (jdbcConnection == null) {
      jdbcConnection =
          JdbcConnectionManager.getJdbcConnection(jdbcConnectionParameters);
    }
  }
}

根据连接检查,您可以按照您的建议在创建后进行。我不会递归调用该方法,只需 null Connection 实例并尝试再次调用 getJdbcConnection()。

【讨论】:

  • 为什么在“.class”而不是“this”?该字段是非静态的,因此每个持久性管理器都将具有其单独的数据层......(它们被实例化为有关连接到何处的信息)。我错了吗?它可能是! :)
  • 我想我对你的问题感到困惑。如果您想要跨所有对象的单一连接,您应该锁定该类。如果你想为每个 JdbcPersistenceManager 实例建立一个连接并确保连接被初始化一次,那么你想锁定它。
  • 当您说不应该允许任何客户端保留对两个不同连接的引用时,您打算如何做到这一点?一旦你实例化了两个对象,你的 Connection 对象是非静态的,你就会有两个不同的连接。
【解决方案2】:

是的,synchronized() 锁只是用于跟踪锁,因此当两个线程看到 jdbcConnection=NULL 时,不会一个接一个地实例化 jdbcConnection。

如果您想在// label:here 位置检查连接的完整性。 可以递归调用getJdbcConnection()方法。

return jdbcConnection!=NULL?jdbcConnection:jdbcConnection();

【讨论】:

  • 我希望 getJdbcConnection 做的是:1)抛出 jdbcConnections,确保在任何给定时间只存在其中一个(不应允许客户端保留对 2 个不同连接的引用)和 2)重新生成(即重新调用 JdbcConnectionManager.getJdbcConnection())“私有易失性连接 jdbcConnection”字段,如果由于某种原因它已关闭(例如,客户端 1 出现,获得连接但 关闭它,客户端 2出现,连接是not null,但不能使用,所以她得到了一个重新生成的)。将此添加到上述要求中...
  • 如果你想要在给定时间一个连接,你应该将 jdbcConnection 设为静态(为什么是 volatile ?)。但是不建议在多个线程之间共享连接。如果它是静态的,连接的重新生成也会被处理。
【解决方案3】:

如果您想实现单例模式,那么您需要确保给定类只有一个实例,这就是单例模式通常使用静态实例实现的原因。

避免任何同步问题的最佳方法是初始化单例内联或在静态初始化程序中,因为初始化发生在第一次访问类时,并且保证此初始化不会受到并发性的影响,即,该类在完全初始化之前不能使用:

public class Singleton {
    private static final Singlenton instance = new Singleton();

    //private constructor here

    public static Singleton getIntance() { return instance; } 
}

现在,如果您想延迟初始化您的单例,那么您将不得不考虑多个线程可能同时尝试获取它的实例的可能性,在这种情况下,您将同步访问您的类:

public class Singleton {
    private static Singlenton instance;

    //private constructor here

    public static synchronized Singleton getIntance() { 
       if(instance == null) {
            intance = new Singleton();
       }
       return instance; 
    } 
}

有些人会建议减少同步代码的范围,这样也可以减少给定线程持有锁的时间。在这种情况下,您可以执行以下操作:

public class Singleton {
    private static Singlenton instance;

    //private constructor here

    public static Singleton getIntance() { 
       synchronized(Singleton.class) {
          if(instance == null) {
               instance = new Singleton();
          }
       }
       return instance; 
    } 
}

在最后一个示例中,一旦安全地分配了单例实例,您就可以同时返回它。

另一种延迟初始化单例的模式是使用静态内部类:

public class Singleton {

  //private constructor

   private static class SingletonHolder { 
       public static final Singleton instance = new Singleton();
  }

   public static Singleton getInstance() {
       return SingletonHolder.instance;
   }
}

这意味着在第一次访问内部类之前不会创建单例,在调用 getIntance 方法之前不会发生这种情况。同样,由于类初始化以线程安全的方式进行,您可以确保单例的创建不会受到影响。

【讨论】:

  • 所有选项的好列表......但你错过了enumtechnique。覆盖它,你就得到了我的投票。
  • @OldCurmudgeon 我不是为了点赞,而是为了好玩。您可以随时在自己的答案中扩展我的答案。这样做,你就得到了我的支持 :-)
【解决方案4】:

除了@edalorzo 的综合列表之外,还有一种技术,使用enum 提供的singleton 机制。有一个很好的例子here,它可能看起来有点像这样。

public enum SingletonConnection {
  INSTANCE;
  // Not sure if this needs to be volatile.
  private volatile Connection jdbcConnection;

  private SingletonConnection() {
    jdbcConnection = JdbcConnectionManager.getJdbcConnection(jdbcConnectionParameters);
  }

  public Connection getConnection() {
    return jdbcConnection;
  }
}

// use it as ...
SingletonConnection.INSTANCE.getConnection ();

但是,您可能对使用ThreadLocal 连接的想法感兴趣。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-03-22
    • 1970-01-01
    • 2016-04-21
    • 2012-06-21
    • 1970-01-01
    • 2011-05-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多