这是一个来自实际项目的例子,在这个案例中,有同事基于jdk中的LinkedHashMap设计了一个LRUCache,为了提高性能,使用了 ReentrantReadWriteLock 读写锁:写锁对应put()方法,而读锁对应get()方法,期望通过读写锁来实现并发get()。

代码实现如下:

  1. private ReentrantReadWriteLock  lock = new ReentrantReadWriteLock ();
  2. lruMap = new LinkedHashMap<K, V>(initialCapacity, loadFactor, true)
  3. public V get(K key) {
  4. lock.readLock().lock();
  5. try {
  6. return lruMap.get(key);
  7. } finally {
  8. lock.readLock().unlock();
  9. }
  10. }
  11. public int entries() {
  12. lock.readLock().lock();
  13. try {
  14. return lruMap.size();
  15. } finally {
  16. lock.readLock().unlock();
  17. }
  18. }
  19. public void put(K key, V value) {
  20. ...
  21. lock.writeLock().lock();
  22. try {
  23. ...
  24. lruMap.put(key, value);
  25. ...
  26. } finally {
  27. lock.writeLock().unlock();
  28. }
  29. }

在测试中发现问题,跑了几个小时系统就会hung up,无法接收http请求。在将把线程栈打印出来检查后,发现很多http的线程都在等读锁。有一个 runnable的线程hold了写锁,但一直停在LinkedHashMap.transfer方法里。线程栈信息如下:

  1. "http-0.0.0.0-8081-178" daemon prio=3 tid=0x0000000004673000 nid=0x135 waiting on condition [0xfffffd7f5759c000]
  2. java.lang.Thread.State: WAITING (parking)
  3. at sun.misc.Unsafe.park(Native Method)
  4. - parking to wait for  <0xfffffd7f7cc86928> (a java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync)
  5. at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
  6. at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)
  7. at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireShared(AbstractQueuedSynchronizer.java:941)
  8. at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireShared(AbstractQueuedSynchronizer.java:1261)
  9. at java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.lock(ReentrantReadWriteLock.java:594)
  10. ......
  11. "http-0.0.0.0-8081-210" daemon prio=3 tid=0x0000000001422800 nid=0x155 runnable [0xfffffd7f5557c000]
  12. java.lang.Thread.State: RUNNABLE
  13. at java.util.LinkedHashMap.transfer(LinkedHashMap.java:234)
  14. at java.util.HashMap.resize(HashMap.java:463)
  15. at java.util.LinkedHashMap.addEntry(LinkedHashMap.java:414)
  16. at java.util.HashMap.put(HashMap.java:385)
  17. ......

大家都知道HashMap不是线程安全的,因此如果HashMap在多线程并发下,需要加互斥锁,如果put()不加锁,就很容易破坏内部链表,造成get()死循 环,一直hung住。这里有一个来自淘宝的例子,有对此现象的详细分析:https://gist.github.com/1081908
    但是在MSDP的这个例子中,由于ReentrantReadWriteLock 读写锁的存在,put()和get()方法是互斥,不会有上述读写竞争的问题。
    Google后发现这是个普遍存在的问题,其根结在于LinkedHashMap的get()方法会改变数据链表。我们来看一下LinkedHashMap的实现代码:

  1. public V get(Object key) {
  2. Entry<K,V> e = (Entry<K,V>)getEntry(key);
  3. if (e == null)
  4. return null;
  5. e.recordAccess(this);
  6. return e.value;
  7. }
  8. void recordAccess(HashMap<K,V> m) {
  9. LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
  10. if (lm.accessOrder) {
  11. lm.modCount++;
  12. remove();
  13. addBefore(lm.header);
  14. }
  15. }
  16. void transfer(HashMap.Entry[] newTable) {
  17. int newCapacity = newTable.length;
  18. for (Entry<K,V> e = header.after; e != header; e = e.after) {
  19. int index = indexFor(e.hash, newCapacity);
  20. e.next = newTable[index];
  21. newTable[index] = e;
  22. }
  23. }

前面LRUCache的代码中,是这样初始化LinkedHashMap的:
lruMap = new LinkedHashMap<K, V>(initialCapacity, loadFactor, true)
    LinkedHashMap构造函数中的参数true表明LinkedHashMap按照访问的次序来排序。这里所谓的按照访问的次序来排序的含义是:当调用LinkedHashMap 的get(key)或者put(key, value)时,如果key在map中被包含,那么LinkedHashMap会将key对象的entry放在线性结构的最后。正是因为LinkedHashMap提 供按照访问的次序来排序的功能,所以它才需要改写HashMap的get(key)方法(HashMap不需要排序)和HashMap.Entry的recordAccess(HashMap)方法。注 意addBefore(lm.header)是将该entry放在header线性表的最后。(参考LinkedHashMap.Entry extends HashMap.Entry 比起HashMap.Entry多了before,  after两个域,是双向的)
    在上面的LRUCache中,为了提供性能,通过使用ReentrantReadWriteLock读写锁实现了并发get(),结果导致了并发问题。解决问题的方式很简单, 去掉读写锁,让put()/get()都使用普通互斥锁就可以了。当然,这样get()方法就无法实现并发读了,对性能有所影响。
   总结,在使用LinkedHashMap时,请小心LinkedHashMap的get()方法。

http://blog.csdn.net/wawmg/article/details/19482041

小心LinkedHashMap的get()方法(转)的更多相关文章

  1. JDK source 之 LinkedHashMap原理浅谈

    注:本文参考JDK1.7.0_45源码. LinkedHashMap是基于HashMap实现的数据结构,与HashMap主要的不同为每个Entry是使用双向链表实现的,并且提供了根据访问顺序进行排序的 ...

  2. SQL Server 2014连接不到服务器解决方法

    多半是不小心使用qq管家之类软件加速系统时把SQL Server(MSSSQL)不小心关闭了 解决方法如下(以WIN8为例):

  3. LinkedHashMap源码详解

    序言 本来是不打算先讲map的,但是随着对set集合的认识,发现如果不先搞懂各种map,是无法理解set的.因为set集合很多的底层就是用map来存储的.比如HashSet就是用HashMap,Lin ...

  4. LinkedHashMap实现LRU算法

    LinkedHashMap特别有意思,它不仅仅是在HashMap上增加Entry的双向链接,它更能借助此特性实现保证Iterator迭代按照插入顺序(以insert模式创建LinkedHashMap) ...

  5. IAR右键无法跳转到定义 的解决方法

    用IAR编译程序,有时候编译通过了,但是右键无法GO TO Definition  解决方法有两个: 第一.Tools -> Option  -> Project 把Generate br ...

  6. 如何用LinkedHashMap实现LRU缓存算法

    阿里巴巴笔试考到了LRU,一激动忘了怎么回事了..准备不充分啊.. 缓存这个东西就是为了提高运行速度的,由于缓存是在寸土寸金的内存里面,不是在硬盘里面,所以容量是很有限的.LRU这个算法就是把最近一次 ...

  7. 【源代码】LinkedHashMap源代码剖析

    注:下面源代码基于jdk1.7.0_11 之前的两篇文章通过源代码分析了两种常见的Map集合,HashMap和Hashtable.本文将继续介绍还有一种Map集合--LinkedHashMap. 顾名 ...

  8. 【JDK1.8】JDK1.8集合源码阅读——LinkedHashMap

    一.前言 在上一篇随笔中,我们分析了HashMap的源码,里面涉及到了3个钩子函数,用来预设给子类--LinkedHashMap的调用,所以趁热打铁,今天我们来一起看一下它的源码吧. 二.Linked ...

  9. Java中常见数据结构Map之LinkedHashMap

    前面已经说完了HashMap, 接着来说下LinkedHashMap. 看到Linked就知道它是有序的Map,即插入顺序和取出顺序是一致的, 究竟是怎样做到的呢? 下面就一窥源码吧. 1, Link ...

随机推荐

  1. cairo graphics.org

    cairographics.org Latest news: 2013-08-26: cairo 1.12.16 snapshot available 2013-02-10: cairo 1.12.1 ...

  2. c++,初始化列表

    类对象的构造顺序是这样的: a.分配内存,调用构造函数时,隐式/显示的初始化各数据成员 b.进入构造函数后在构造函数中执行一般计算 1.初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内 ...

  3. checkbox探究

    介绍checkbox checkbox: A check box. You must use the value attribute to define the value submitted by ...

  4. 组件接口(API)设计指南[4]-通知(Notifications)

    *返回文件夹阅读其它章节: http://blog.csdn.net/cuibo1123/article/details/39894477 通知(Notifications) 通知是托付协议的还有一半 ...

  5. Hadoop MapReduce编程的一些个人理解

    首先要实现mapreduce就要重写两个函数,一个是map 还有一个是reduce map(key ,value) map函数有两个參数,一个是key,一个是value 假设你的输入类型是TextIn ...

  6. perl post 请求带参数

    my $url='https://wenjinbao.winfae.com/business/dispatch_post.do?action=submitAdminLogin';    my $res ...

  7. Windows Azure 数据安全(清理和泄漏)

    免责声明:本文档中所述过程为 2012 年 1 月时起的情况,如有变更,恕不另行通知. 希望将应用程序部署到 Windows Azure 的企业客户(实际上是所有客户)最为关心的就是其数据的安全性.释 ...

  8. 消息机4_B

    /******************************************************************** author : Clark/陈泽丹 created : 2 ...

  9. Mac上利用Eclipse编译Cocos2d-x

    目前使用较多的Cocos2d-x开发平台是XCode,应该是由于大部分Cocos2d-x开发者都是iOS开发出生.但是当我们将XCode开发的Cocos2d-x工程发布Android版本时,每次都需要 ...

  10. crm使用FetchXml聚合查询

    /* 创建者:菜刀居士的博客  * 创建日期:2014年07月08号  */ namespace Net.CRM.FetchXml {     using System;     using Micr ...