使用Guava为并发应用实现基于对象的微锁
编写并发程序最烦人的问题就是处理线程共享的资源,比如在一个Web应用中,Session会话中保存数据,使用下面代码方式:
HttpSession session = request.getSession(true);
if (session.getAttribute("shoppingCart") == null) {
synchronize(session) {
if(session.getAttribute("shoppingCart")= null) {
cart = new ShoppingCart();
session.setAttribute("shoppingCart");
}
}
}
ShoppingCart cart = (ShoppingCart)session.getAttribute("shoppingCart");
doSomethingWith(cart);
此代码是将一个购物车对象加入到Session中,当在Session中没有发现一个购物车实例,该代码在当前用户的会话中获得一个监视器,然后添加一个新的ShoppingCart到当前用户的HttpSession中。该解决方案有几个缺点:
只要调用到该代码的任何线程将被阻塞。如果当两个线程试图访问不同的Session中值,这会发生堵塞。找一个不是HttpSession实例的对象用来同步要好得多,但是在两个线程之间总是要共享同一个对象资源,一个好的方法是使用Guava的内在并发机制,可以使用weak弱键:
LoadingCache<String, Object> monitorCache = CacheBuilder.newBuilder()
.weakValues()
.build(
new CacheLoader<String, Object>{
public Object load(String key) {
return new Object();
}
});
这是一段使用Guava缓存的代码,那么我们就使用这个LoadingCache对象作为Session中共享锁,替代之前的synchronized 。
代码如下:
HttpSession session = request.getSession(true);
Object monitor = ((LoadingCache<String,Object>)session.getAttribute("cache"))
.get("shoppingCart");
if (session.getAttribute("shoppingCart") == null) {
synchronize(monitor) {
if(session.getAttribute("shoppingCart")= null) {
cart = new ShoppingCart();
session.setAttribute("shoppingCart");
}
}
}
ShoppingCart cart = (ShoppingCart)session.getAttribute("shoppingCart");
doSomethingWith(cart);
Guava的缓存是self-populating ,能够简单地返回一个对象实例,被我们用来作为Session中共享锁,Guava缓存后面是ConcurrentHashMap,可以避免一般的同步,而只是在map的主键哈希值上同步,这样使用它的应用程序就没有全局锁,而且线程安全。
同时你不必担心out of memory问题,因为监视器对象如果不再被使用将会被垃圾回收。
当然上面机制还可以继续完善,比如返回ReadWriteLock实例等,在Session开始时初始化一个LoadingCache也很重要,可以使用HttpSessionListener等等。
java多线程
Java同步或锁
Java性能调优