并发主题

使用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性能调优