`
san_yun
  • 浏览: 2594954 次
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

exodus2线程总结

 
阅读更多
现象描述
exodus2在启动不久后就挂住,用kill -3后发觉线程大量停在了Hashmap.get/put 方法。 原因是随意将HashMap用于多线程环境中


代码:

定义成员变量

private static Map cachedMap = new HashMap(7000);

private static Boolean firstInvoke = true;



程序是设想在第一次开始对该map变量进行初始化

线程1:

Public Object  getMyValue(){

     If(firstInvoke){

        While(i<7000){

…………

cachedMap.put("new","newValue");

i++;

}

firstInvoke = false;

}

}


线程2:

在线程1对cachedMap对象put的时候,线程2从这个cachedMap中取值

cachedMap.get("new");

问题分析:

单步Debug是没问题,但代码在多线程情况下工作会出现线程安全。 Hashmap不是读写线程安全的,只有全部只读才是线程安全的,Hashmap在被并发读写使用的时候会出现线程安全问题,一般理解的线程安全问题导致的是数据错误。 而Hashmap多线程同时读写操作时,可能使程序挂起。

以下摘自校长bolg( http://sdh5724.javaeye.com/blog/619130 )
分析: 我们知道Hashmap在被并发读写使用的时候, 会抛出ConcurrentModificationException这个异常, 但是JDK文档明确指出, 这个异常抛出是属于 fail-fast 的一个设计方法, 目的是为了开发者能及早的意识到线程安全问题发生。 但是, 这个fail-fast不是一定会发生, 而是可能会发生的行为。 因此, 在一个不确定状态下的下,jvm线程发生持续100%cpu行为是比较容易理解了(for (Entry<K,V> e = table[i]; e != null; e = e.next), 目前只能估计是这个代码进入死循环的状态,还不能非常明确)

补充知识
这个HashMap不当使用的问题很经典。很多时候我们用“单线程”思维习惯去写代码,不知不觉就忘记了运行时的多线程场景。

其实,我觉得下面的例子中还是有隐含的race condition问题的,那就是在这个if(firstInvoke) then load data and firstInvoke=false这个逻辑中。

即:If(firstInvoke){…  // 这里可能会导致多条线程同时进入,导致多次load data

通常我们用一个boolean变量来实现lazy操作, 那么在多线程环境下,要记得使用synchronize关键词 或者 采用volatile类型变量+CAS操作,确保变量被每条线程都能正确的读取和写入。

    1.保险的做法:(在最新JVM中,这种方式是最安全,最可读,性价比最高的,如果JVM支持锁逃逸即Biased Locking,性能也会非常好)

Synchronized(lock){

         If(firstInvoke){

             Then load data…

             firstInvoke = false

}

}

    2.或者,用volatile变量+DCL

Private volatile boolean firstInvoke = true;

If(firstInvoke){

     Synchronized(lock){

       If(firstInvoke){

          Then load data …

           firstInvoke = false;

}

}

}

3.SMP友好,但是偷懒的做法,用AtomicBoolean,里面用到了CompareAndSet操作。(volatile只保证变量可见性,Spinning CAS保证操作原子性)

Private AtomicBoolean firstInvoke = new AtomicBoolean(true);

If(firstInvoke.getAndSet(false)){ // cas spinning inside the AtomicBoolean::getAndSet() method

  Then load  data…

}

4.最后,最复杂,但是同时满足SMP友好,及性能最佳的:

private AtomicBoolean firstInvoke = new AtomicBoolean(true);

for(;;){

  Boolean current = firstInvoke.get();

  If(!current){     // the most likely condition branch, see http://pt.alibaba-inc.com/wp/dev_related/optimization_363/likely-unlikely.html

     Break;

}

If(firstInvoke.compareAndSet(current,false){

   Then load data…

   Break;

}

}

在dubbo代码中,为了确保SMP状态下性能最优,我们在某一些关键地方也用到了上面的CAS+spinning的技巧。

我们也许并不会时时刻刻用到“回字的四种写法”,但是搞清楚JVM内存可见性和操作原子性的基本概念还是必须的,这也是确保写出线程安全代码的前提条件)。

参考资料:《 The Art of Multiprocessor Programming》  http://book.douban.com/subject/3024605/

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics