2012-08-10 30 views
1

因为我没有发现涉及此主题的问题,所以我想我会将我的解决方案分享给以下方案。答案可能很明显,但我花了很长的路要找出答案。 :)我会很感激问题和答案以及其他解决方案的反馈。在惰性加载Getter上同步

场景:

假设你有一个多线程的程序,并希望在你的程序对一些功能的数据库连接(或其他一些共享对象),而你的程序的其他部分并不需要它所有。应该只有一个连接到数据库,但。

与此同时,您想要检测db连接丢失并尝试重新连接。

为了解决这个问题,你需要实现一个延迟加载模式“getter”,它在返回连接对象之前也检查连接的有效性。

您的代码可能是这样的:

public class Main { 
    private DB _db; 

    public static void main(String[] args) { 
    new Main().start(); 
    } 

    private void start() { 
    // Program code goes here 
    // You create several threads, some of which may call getDB() whenever they need DB access 
    } 

    public DB getDB() { 
    if (_db == null) { 
     _db = getDBConnection(); 
    } else if (!_db.isConnectionValid()) { 
     /* 
     * DB connection is not valid anymore. Let's close it and 
     * try to get a new connection. 
     */ 
     _db.close(); 
     _db = getDBConnection(); 
    } 

    return _db; 
    } 

    private DB getDBConnection() { 
    DB db; 

    // Obtain a new connection... 
    ... 

    return db; 
    } 
} 

问题

的多个线程可能会试图获得在几乎同一时间一个数据库连接。当某些类保持对它们的引用时,甚至有可能多个连接共存。

回答

1

几个线程可能会尝试几乎同时获得数据库连接。当某些类保持对它们的引用时,甚至有可能多个连接共存。

在这种情况下,您需要一个池,因为您可以获得多个不同的实例。有许多DatabaseConnection池可用,并且一些JDBC驱动程序有其自己的。我建议你使用JDBC驱动程序附带的一个,或者使用C3P0等作为数据库连接池。

更具体地说,你需要采取的方式,另一个线程不能得到相同的连接的连接(不只是得到它)。一个简单的例子是使用队列。

private final Queue<DB> freeDBs = new ConcurrentLinkedQueue<>(); 

public DB acquireDB() { 
    DB db = freeDBs.poll(); 
    if (db != null && db.isConnectionValid()) 
     return db; 
    if (db != null) 
     db.close(); 
    return getDBConnection(); 
} 

public void release(DB db) { 
    if (freeDBs.size() >= MAX_FREE_SIZE) 
     db.close(); 
    else 
     freeDBs.add(db); 
} 
+0

这可以结合“信号量”类吗?这样做是否合理? – riha 2012-08-13 05:57:25

1

可以使用同步来避免同时创建多个连接。如果两个(或更多)线程几乎同时调用它,则其中一个线程会阻塞(等待),直到另一个线程完成。这可以确保第二个线程获取刚刚由第一个线程创建的连接,而不是建立另一个连接。

我第一次尝试这样的对象同步:

public DB getDB() { 
    synchronized (_db) { 
    if (_db == null) { 
     _db = getDBConnection(); 
    } else if (!_db.isConnectionValid()) { 
     /* 
     * DB connection is not valid anymore. Let's close it and 
     * try to get a new connection. 
     */ 
     _db.close(); 
     _db = getDBConnection(); 
    } 
    } 

    return _db; 
} 

这里的问题是,不与延迟加载工作。您无法同步null(您会收到NullPointerException),但在第一次呼叫getDB()时没有任何对象。

的解决方案是对整个方法同步:

public synchronized DB getDB() { 
    if (_db == null) { 
    _db = getDBConnection(); 
    } else if (!_db.isConnectionValid()) { 
    /* 
    * DB connection is not valid anymore. Let's close it and 
    * try to get a new connection. 
    */ 
    _db.close(); 
    _db = getDBConnection(); 
    } 


    return _db; 
} 

此外,您需要确保没有其他的方法来访问私有字段_db或直接致电getDBConnection()。那将不再同步。

您的类不应该保留对连接的引用,因为这样可以防止死连接对象上的垃圾回收。通常不建议调用getter,因为每个get都可能发出查询来检查连接有效性(取决于驱动程序)。如果每个方法在执行过程中都保留一个引用(除非它执行了很长时间),那很可能是正确的。

+2

所有这可能是罚款,一个学习的经验,但你真的要在生产什么是严重**连接池**执行,将采取的照顾问题,你在这里指出,以及大范围的其他您即将发现的问题。 '骨骼CP'是我目前最喜欢的。 – 2012-08-10 10:06:53

+0

您仍然有多个线程可以获得相同连接的问题。 – 2012-08-10 10:10:59

1

那么这里是我的2C:

首先,关于你所使用的同步对象实例:如果您使用_db对象,在这个意义上是不好的,你不会得到你想要的。这里的想法是确保如果多个线程尝试同时创建一个_db实例(就JDK进程而言),一旦其中一个线程创建一个实例,其他线程应立即知道该实例是否存在,而不是尝试创建另一个实例。现在,如果您在该实例上同步代码块,我们试图在线程之间进行同步,即使所述实例永远不会为null,您仍然处于竞争状态,两个线程各自设法创建_db的实例,并且由于代码块在该实例上是同步的,所以没有任何线程会被锁阻塞,因为确实有两个单独的锁。 显然,最好是同步整个方法。这相当于编写

public DB getDB() { 
     synchronized (this) { 
      if (_db == null) { 
       _db = getDBConnection(); 
      } else if (!_db.isConnectionValid()) { 
       /* 
       * DB connection is not valid anymore. Let's close it and 
       * try to get a new connection. 
       */ 
       _db.close(); 
       _db = getDBConnection(); 
      } 
      return _db; 
     } 
    } 

的所有线程调用创建的_db实例的方法将“打”在同一个锁(主类的实例),所以你可以肯定的是,一旦一个线程获得该锁其他人将阻塞,直到该线程完成,然后,当轮到他们执行该方法时,if检查将阻止他们创建_db对象的第二个实例。现在

,另一个问题是天气,你真的想在多个线程在同一_db实例。这个问题真的是简化为天气_db是线程安全的,换句话说,它是无状态的吗?如果它是有状态的并由多个线程共享,并且如果该状态不能防范多线程调用,则会出现奇怪的行为,甚至出现错误。例如:JDBC Connection对象不是线程安全的,因为它包含事务的状态,例如事务,如果多个线程同时访问相同的JDBC Connection,可以无法改变这些事务。由于这个原因,建议在多线程环境中使用JDBC连接时使用一定程度的(对象实例)隔离。要么你普通的旧为每个线程新的JDBC连接实例或你做只有一个,但后来每个线程里面将保留作为一个ThreadLocal字段,以便每个线程真正得到他自己的实例,它只有他自己才改变/访问。

另一个例子是HasmMap和ConcurrentHashMap的。在这里,如果你使用相同的HashMap的多线程,你一定会得到错误(例如,如果在另外一个试图修改它,你会得到一个并发的修改除了一个线程itterates地图项),或者如果没有错误,至少一个巨大的性能瓶颈,因为由于多个线程发送了多个写入,Map将执行大量重新hashig。另一方面,ConcurrentHashMap非常适合在多个线程之间共享一个实例。您不会得到并发修改异常,并且在多个线程同时写入时,Map的性能会更好。

+0

换句话说,同步块不会在引用'_db'上同步,而是在块尝试执行时由它引用的对象同步? – riha 2012-08-13 05:51:21

+0

@riha我不知道我明白你的意思。同步BLOCKS使用锁作为参数传递的实例。同步方法锁定具有该方法的类的实例(并且相当于“synchronized(this)”块)。同步静态方法使用encompasing类作为锁 – 2012-08-13 20:22:28

+0

是的,谢谢。之前,我认为synchronized块锁定了传递的引用,而不是分配给它的对象实例。这实际上是非常愚蠢的,现在我想到了它...哦,并感谢清理“连接”不是线程安全的。我并没有真正意识到这一点。 – riha 2012-08-15 06:03:52