【问题标题】:Using synchronization in Data Access Layer在数据访问层中使用同步
【发布时间】:2010-12-07 20:08:13
【问题描述】:

假设我们正在开发实现简单 CRUD 操作以使用 DB 的类。此类还维护缓存以提高性能。

public class FooTableGateway {
   Map<Integer, Foo> id2foo = new HashMap<Integer, Foo> ();
   public void getFoo (int id) {
      if (id2foo.containsKey (id) {
          return id2foo.get (id);
      }
      String query = "select ...";
      Connection cn = null;
      Statement st = null;
      ResultSet rs = null;
      try {
          cn = DBUtils.getConnection ();
          st = cn.createStatement ();
          rs = st.executeQuery (query);

          if (!rs.next ()) {
              return null;
          }
          Foo foo = new Foo (rs.getString (1)...);
          id2foo.put (id, foo);
          return foo;
      } catch (SQLException e) {
          ..
      } finally {
          ..
      }
   }

   public boolean addFoo (Foo foo) {
      if (id2foo.values ().contains (foo) {
           return false;
      }
      String query = "insert into ...";
      Connection cn = null;
      Statement st = null;
      ResultSet rs = null;
      try {
          cn = DBUtils.getConnection ();
          st = cn.createStatement ();
          int num = st.executeUpdate (query.toString (),
                  Statement.RETURN_GENERATED_KEYS);
          rs = st.getGeneratedKeys ();
          rs.next ();  
          foo.setId (rs.getInt (1);
          id2foo.put (foo.getId (), foo);
          return true;
      } catch (SQLException e) {
          ..
          return false;
      } finally {
          ..
      }    
   }

   public void updateFoo (Foo foo) {
      //something similar
      ..
   }

   public boolean deleteFoo (int id) {
      //something similar
      ..
   }

}

问题是:代码的哪一部分应该同步? (当然,我们正在开发网络应用程序)。

如果我将所有调用同步到缓存集合,那么我什至不确定使用缓存是否会提高性能。

【问题讨论】:

    标签: java performance caching synchronization data-access-layer


    【解决方案1】:

    这段代码写出来的问题太多了。

    我不认为 DAO 应该与获取到数据库的连接有任何关系;它应该被传入或注入到类中。 DAO 无法知道它是否在更大的事务上下文中使用。一个单独的服务层,其方法对应于了解工作单元的用例,应该负责获取连接、设置事务和隔离、编组 DAO 和业务实体以完成用例、提交或回滚事务,并清理资源。

    你在这里做了很多事情:持久性、缓存等等。如果你能开始剥离其中的一些责任并将它们放在其他地方,你的生活将会变得更好。我认为你的网关做得太多了。

    更新:

    你扔进课堂的地图告诉我这是一个巨大的错误。我没有看到任何 SoftReferences 来帮助垃圾收集器。我没有看到任何限制缓存大小或在更新值时刷新值的努力。这是一种自找麻烦的方法。编写缓存是一项艰巨的任务。如果您不相信我,请下载 EhCache 的源代码并将其与您的地图进行比较。这不是微不足道的。

    声明式事务没有逻辑 - 另一个巨大的错误。

    恕我直言,我会重新考虑这个实现。

    更好的建议是学习 Spring 和/或 Hibernate。

    【讨论】:

    • 我讨厌 DAO,我喜欢 Fowler 的模式
    • 不管怎样,你们班做的太多了。
    • 讨论你为什么这么认为对我来说真的很有趣,如果你有兴趣 - 试着解释一下,也许我们会为彼此找到新的东西))
    • 我同意。这实际上是面向对象编程的原则之一:en.wikipedia.org/wiki/Single_responsibility_principle 使代码的扩展、重用和维护变得更加容易。
    • 在同一种方法中使用缓存和数据库是正常的,我看不出将它分成两种(甚至更多)不同的方法有什么好处。
    【解决方案2】:

    您还可以查看 JDK 5+ RWLs。引用Wikipedia

    在这种模式下,多个读取器可以并行读取数据,但在写入数据时需要排他锁。当 writer 写入数据时,reader 将被阻塞,直到 writer 完成写入。

    请务必查看使用 R/W 锁的潜在陷阱,例如this Java 专家时事通讯。

    【讨论】:

      【解决方案3】:

      ChrisW 用他的answer 解决了这个问题——你需要保护共享状态不被多个线程访问和修改。在这个例子中你的共享状态是实例级 Map

      Map<Integer, Foo> id2foo = new HashMap<Integer, Foo> ();
      

      您正在用作缓存。同步对 this 的访问和修改将使其成为线程安全的。

      您可以采取的另一种方法是 使用一些更高级别的 可用的非阻塞实用程序 Java Concurrent Utils api。

      具体来说,看一下ConcurrentHashMap,它允许并发读取而不阻塞和可调整的并发更新。

      在您的情况下,这将是 HashMap 的替代品。 ConcurrentMap 定义了原子非阻塞V putiFAbsent(T key, V value) 方法用于添加到缓存中,您可以从多个线程中安全地读取它而无需锁定。

      【讨论】:

      • 谢谢!我将回顾一些流行的缓存实现,但你第一次接近就足够了。
      【解决方案4】:

      问题是:应该同步哪部分代码?

      与往常一样,您需要同步对数据的访问,这些数据由一个线程修改,同时由另一个线程修改甚至读取。

      在此示例中,共享数据是您的 id2foo 字典。因此,请锁定以下语句:

      • 这里有一个:

          if (id2foo.containsKey (id) {
              return id2foo.get (id);
          }
        
      • 这里是另一个:

          id2foo.put (id, foo);
        

      为了最大化并发性(即最小化锁争用),您应该使这些锁的生命周期尽可能短:即仅围绕我上面列出的少数语句,而不是围绕整个 getFooaddFoo 方法.

      但请注意,使用缓存执行此操作可能会导致数据过时;无论如何,数据库都可能发生这种情况(取决于“事务隔离级别”),但要小心。

      如果我将所有调用同步到缓存集合,那么我什至不确定使用缓存是否会提高性能。

      在我看来,它可能会提高性能:假设您没有在缓存中存储太多数据,那么从缓存中读取所花费的时间要比从数据库中读取要少得多,即使您需要等待缓存上的锁定,特别是如果缓存上的锁定是短暂的,正如我所建议的那样。

      如果你想要花哨,你可以使用多读/单写锁,这样当多个线程从缓存中读取而没有人写入缓存时,不会发生争用。

      【讨论】:

      • 感谢第一个有意义的回答,尤其是最后一段。
      【解决方案5】:

      哇...单个方法的代码量很大。我真的建议将其分解为方法和对象一次做某事。

      在上面给出的代码中,您应该在读取、写入和删除时同步缓存集合;这将锁定缓存,因此无法进行并行读取。

      编写高性能的线程安全缓存并不容易(尤其是如果您现在或将来需要将其集群化)。你真的应该看看现有的,比如 EHCache (http://ehcache.org/) 或 JBoss Cache (http://jboss.org/jbosscache/)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多