假设我们正在开发实现简单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
..
}
}
问题是:应该同步哪部分代码? (当然,我们正在开发网络应用程序。)
如果我将所有调用同步到缓存集合,那么我甚至不确定使用缓存会提高性能。
答案 0 :(得分:3)
问题是:应该同步哪部分代码?
与往常一样,您需要同步对数据的访问,这些数据由一个线程修改并同时被修改,甚至只是被另一个线程读取。
在此示例中,共享数据是您的id2foo字典。因此,请锁定以下声明:
一个人在这里:
if (id2foo.containsKey (id) {
return id2foo.get (id);
}
另一个在这里:
id2foo.put (id, foo);
为了最大化并发性(即最小化锁争用),您应该使这些锁的生命周期尽可能短:即仅围绕我上面列出的几个语句,而不是围绕整个getFoo
和{{ 1}}方法。
请注意,使用缓存执行此操作可能会使您获得过时的数据;无论如何,这可能发生在数据库中(取决于“事务隔离级别”),但要小心。
如果我将所有调用同步到缓存集合,那么我甚至不确定使用缓存会提高性能。
在我看来, 可能会提高性能:假设您没有在缓存中存储太多数据,从缓存中读取数据所花费的时间要比从数据库中读取要少得多,即使您需要等待缓存锁定,特别是如果缓存上的锁定是短暂的,正如我所建议的那样。
如果你想要花哨,可以使用多阅读器/单一写入器锁,这样当多个线程从高速缓存读取而没有人写入高速缓存时,没有争用。
答案 1 :(得分:1)
哇......这是单个方法的很多代码。我真的建议将它分解为一次做事物的方法和对象。
在上面给出的代码中,您应该在读取,写入和删除时同步缓存集合;这将锁定缓存,因此无法进行并行读取。
编写高性能的线程安全缓存并不容易(特别是如果您现在或将来需要将其集群化)。你应该真正看看现有的,如EHCache(http://ehcache.org/)或JBoss Cache(http://jboss.org/jbosscache/)。
答案 2 :(得分:1)
ChrisW使用answer对其进行了攻击 - 您需要保护共享状态不被多个线程访问+修改。此示例中的共享状态是实例级别Map
Map<Integer, Foo> id2foo = new HashMap<Integer, Foo> ();
您正在用作缓存。同步访问和修改它将使其线程安全。
你可以采取的另一种方法是 使用一些更高级别 非阻塞实用程序 Java Concurrent Utils api。
具体来说,看看ConcurrentHashMap允许并发读取而不阻塞和可调整的并发更新。
在您的情况下,这将是HashMap的替代品。 ConcurrentMap定义了用于添加到缓存的原子非阻塞V putiFAbsent(T key, V value)
方法,您可以安全地从多个线程中读取它而不会锁定。
答案 3 :(得分:0)
此代码存在太多问题。
我认为DAO不应该与获取数据库连接有任何关系;它应该传入或注入课堂。 DAO无法知道它是否在更大的事务环境中使用。一个单独的服务层,其方法对应于了解工作单元的用例,应该是负责获取连接,设置事务和隔离,编组DAO和业务实体以完成用例,提交或回滚事务的服务层,并清理资源。
你在这里有很多事情:持久性,缓存等等。如果你能开始剥离其中的一些责任并把它们放到其他地方,你的生活会更好。我认为你的网关做得太多了。
更新:
你扔进课堂的地图告诉我这是一个很大的错误。我没有看到任何SoftReferences来帮助垃圾收集器。我没有看到任何限制缓存大小或更新时刷新值的努力。这是一种乞求麻烦的方法。编写缓存是一项重大努力。如果您不相信我,请下载EhCache的源代码并将其与您的Map进行比较。这不是微不足道的。
声明性交易没有逻辑 - 另一个巨大的错误。
在充分尊重的情况下,我会重新考虑这种实施。
更好的建议是学习Spring和/或Hibernate。
答案 4 :(得分:0)