`
kekeemx
  • 浏览: 8561 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

多线程下lrumap的操作经验

阅读更多
   前些时候用到了apache的Lrumap,用来维护N个用户的定购关系列表,大致是Lrumap<string,List<string>>后来测试的时候出现了一个很奇怪的问题,就是系统运行一段时间后会发现某些用户的某些定购关系在内存中不存在,但是数据库中却是有值的。开始的时候我怀疑用户名id(即map的key)所对应的List空掉了,最后想了很久发现应该是值缺失了一部分,而不是空掉了。


下边是一个经过抽象后的简单模型:

Map<String, List<String>> m = new LruMap<String, List<String>>; 
// 由于最新的apache 的common-collection3.2.1包中的lrumap是不同步的.
// 所以我想当然的对其进行了一次同步。
    boolean foo(value) { 
        List<String> list; 
        synchronized (m) { 
            list = m.get(uid); 
        } 
        if (list == null) { 
            list = getdataFormDatabase(); // 将其放在外边是由于这块可能非常慢,而并发又很严重,考虑到性能就放同步块外边了
            synchronized (m) { 
                m.put(uid, list); 
            } 
            return list.contains(value); 
        } else { 
            return list.contains(value); // 问题出现在这里,返回的是false,实际上应该为true
        } 
    } 

    foo(uid, value) { 
        List<String> list; 
        synchronized (m) { 
            list = m.get(uid); 
        } 
        if (list == null) { 
            list = getDataFromDatabase(); // 理由同上
            list.add(value); 
            synchronized (m) { 
                m.put(uid,list); 
            } 
        } else { 
            list.add(value); 
        } 
    }  
分享到:
评论
8 楼 kekeemx 2009-03-27  
dennis_zane 写道
我觉的你是简单问题复杂化了,在我看来,这个错误最简单的修正方式就是扩大锁的范围,在没有明确测试之前怎么能确定一定有性能上的问题呢?况且从数据库加载数据这一操作也只是一次调用。采用cas方式不一定能带来性能提升,但是代码复杂度的提升倒是显而易见。

因为我这个系统的业务比较复杂,中间掺杂了很多特殊的业务处理都是非常耗时的(包括一次远程ice调用和若干个无法避免的数据库写操作)所以对这个查询的性能(即模型中第一个用于判断的foo方法)要求必须稳定而且是尽量能越小越好,  我大概在测试环境的服务器上测试了一下,这块getDatafromDb的时间消耗很不稳定(因为测试环境跑了很多其他服务,应用服务器和db的负载很不稳定.所以这个测试结果也只能大略参考一下)。性能主要看数据库当时负载,查询的结果集大小等因素.从1ms到10+ms不等. 虽然getDatafromdb对一个用户而言只会执行一次(不考虑lru),但是
在高并发的情况下,这块的性能表现可能就很不稳定了.很有可能是时快时慢. 所以我认为, 令可增加程序的复杂度,也
需要尽量保证其稳定性。对我这里map的get和put操作来说,string作为key应该可以保证其性能。(按照之前做过map的get和put性能测试来看还是很理想的)
7 楼 kekeemx 2009-03-27  
sdh5724 写道
高并发的对象控制还是需要很多经验的。 我们最近做了不少多线程的代码, 确实不同程序员反应出来的经验差异很的。 要控制线程安全, 不是那么简单的事情, 特别是你处要注意锁与性能的问题。 如果不考虑性能, 处处有LOCK也行。。。。

的确.之前也写了不少多线程方面的代码,感觉并发控制不是那么困难, 但是经此一役,发现自己还差很远.呵呵.

6 楼 dennis_zane 2009-03-26  
我觉的你是简单问题复杂化了,在我看来,这个错误最简单的修正方式就是扩大锁的范围,在没有明确测试之前怎么能确定一定有性能上的问题呢?况且从数据库加载数据这一操作也只是一次调用。采用cas方式不一定能带来性能提升,但是代码复杂度的提升倒是显而易见。
5 楼 sdh5724 2009-03-26  
高并发的对象控制还是需要很多经验的。 我们最近做了不少多线程的代码, 确实不同程序员反应出来的经验差异很的。 要控制线程安全, 不是那么简单的事情, 特别是你处要注意锁与性能的问题。 如果不考虑性能, 处处有LOCK也行。。。。
4 楼 kekeemx 2009-03-26  
修改完成之后, 还没有经过实际的检验, 不过这个put操作 可能引起list值缺失的情况我倒是通过几个多线程测试例子验证了。的确会发生这种情况,不过现在怎么看这个修改怎么都觉得不太优雅。不知道大牛门有没有更好的方法。
3 楼 kekeemx 2009-03-26  
  问题找是找到了.怎么解决呢?

  估计最简单的是增大同步块,将getdatafromdb的操作一起都同步了就没问题了。不过考虑到性能(因为系统会对这个lrumap有非常高的并发操作.大部分都是想执行get操作),这个一同步,外部很多线程都会被阻塞.系统估计就差不多down掉了。

   后来想到了CAS(compare and swap貌似是一个原子操作).大概修改思路类似于cas,就是put之前再次看看是否已经有线程put值进去了。不多说,看代码大家就明白了。
    boolean foo(value) { 
        List<String> original; 
        synchronized (m) { 
            original = m.get(uid); 
        } 
        if (original == null) { 
            original = getdataFormDatabase(); // 这里返回的list是 同步的 list 
            List<String> modifys = null; 
            synchronized (m) { 
                modifys = m.get(uid); 
                if (modifys != null) { 
                    // saysomething here but no need to addall here; 
                } else { 
                    m.put(uid, original); 
                } 
            } 
            if (modifys != null) { 
                if (original.size > modifys.size) { 
                    modifys.addAll(original); // 尽量减少一次addAll操作,因为addall在数据量很大的情况下也是性能瓶颈,当然我这里数据量撑死了1w,不过为了那么一点点性能,还是判断一下
                } 
                return modifys.contains(value); 
            } 
            return original.contains(value); 
        } else { 
            return original.contains(value); 
        } 
    }

基本的原则是,只要某个uid的list被put过一次,之后就只能修改这个list而不会被新的put操作给替换掉。

同理,添加的操作也做对应的修改:
foo(uid, value) { 
        List<String> original; 
        synchronized (m) { 
            original = m.get(uid); 
        } 
        if (original == null) { 
            original = getDataFromDatabase(); // 这里返回的list是 同步的 list 
            original.add(value); 
            List<String> modifys = null; 
            synchronized (m) { 
                modifys = m.get(uid); 
                if (modifys != null) { 
                    // saysomething here but no need to addall here; 
                } else { 
                    m.put(uid, original); 
                } 
            } 
            if (modifys != null) { 
                if (original.size > modifys.size) { 
                    modifys.addAll(original); 
                } 
                modifys.add(value); 
            } else { 
                original.add(value); 
            } 
        } else { 
            original.add(value); 
        } 
}
2 楼 kekeemx 2009-03-26  
下边来简单说明一下我认为的问题所在(各位大牛可以看看是否如此)

 synchronized (m) { 
            list = m.get(uid); // 步骤1
        } 
        if (list == null) { 
            list = getdataFormDatabase(); // 步骤2
            synchronized (m) { 
                m.put(uid, list); //问题出在这里
            } 

首先, 假设我们现在有2个线程(线程1,和线程2),先后获得m的使用权通过了步骤1,这样两个线程都会执行步骤2,好了,问题开始了。
线程1顺利执行完成 getdatafromdb的操作, 获取了listA数据,此时数据是完整的。并且再次获取了m的使用权,将其put进入map中, 此时线程2也顺利提取完成获得了另外一个listB。他准备将自己的listB也put进map。

当当当,这个时候线程3突入闯入进来, 并且获取了m的使用权,执行了步骤3,发现内存有值了(listA),于是顺利执行了步骤4。
 synchronized (m) { 
            list = m.get(uid); // 步骤3
        } 
        if (list == null) { 
            list = getDataFromDatabase(); 
            list.add(value); 
            synchronized (m) { 
                m.put(uid,list); 
            } 
        } else { 
            list.add(value);  // 步骤4  
        } 
 
其实本来listA的内容跟listB一样, 但是由于线程3的干扰,listA的内存中有了一个新的值.但是线程3执行完成后走掉了, 线程2终于轮到m的使用权,并且将listB重新put进入map,顶替掉了listA。悲剧就这样产生了.

假设之后再也不出现这种情况(我们实际系统中理论上是不会产生一个用户id同时执行foo这2个方法的),而且这个id又一直没有被lrumap清理掉, 就会导致内存值永久的缺失了一个值。就会出现之前的问题 了。

1 楼 kekeemx 2009-03-26  
如果并发经验比较丰富的大牛一眼就会看出问题所在,
不过我开始的时候一直是认为用户id对应的List没有同步才导致的值丢失,就出现了调用foo(value)方法返回false.当然了.这里list肯定也是需要同步的.但是问题的根源我后来想了很久发现还不是在于这个list没有同步。

问题应该出现在对 getDatafromDb()操作上.这个放到同步块外边会导致后边的put(uid,list)产生问题。

相关推荐

    多线程map容器互斥代码

    //使用多线程,定时器和map等编程技巧。 // 定义map对象来存储数据,主线程中定时向该对象插入数据,每次插入两个连续的数据插入后显示出当前map大小。 //第二个线程定时删除map中的数据,每次删除一个最前面的数据...

    java 多线程操作数据库

    一个java 多线程操作数据库应用程序!!!

    Java多线程编程经验

    现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。 线程是指进程中的一个执行流程,一个进程中可以运行多个线程。...本文档提供Java多线程编程经验,方便广大Java爱好者研究学习Java多线程

    适合初学者的QT多线程操作的例子

    适合初学者的QT多线程操作的例子 适合初学者的QT多线程操作的例子

    c_多线程 c_多线程

    c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程c_多线程...

    易语言多线程操作

    易语言多线程操作源码,多线程操作,子程序1

    面试题c++mysql多线程操作系统面试题c++mysql多线程操作系统面试题c++mysql多线程操作系统

    面试题c++mysql多线程操作系统 面试题c++mysql多线程操作系统 面试题c++mysql多线程操作系统 面试题c++mysql多线程操作系统 面试题c++mysql多线程操作系统

    多线程_按键精灵经典多线程操作_

    按键精灵经典多线程操作,可以套用于任意的其他脚本上

    C#多线程互斥实例 多线程获取同一变量

    C#多线程互斥实例 多线程获取同一变量(不重复)。是一个很好的学习例子

    QT中sqlite多线程操作4个注意问题

    总结了一下Qt中sqlite多线程操作遇到的几个问题,希望能对有需要的朋友一点帮助 总结了一下Qt中sqlite多线程操作遇到的几个问题,希望能对有需要的朋友一点帮助

    C#多线程 C#多线程

    多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程多线程

    高吞吐、线程安全的LRU缓存详解

    主要介绍了高吞吐、线程安全的LRU缓存详解,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下

    多线程操作窗口源码2012813

    多线程操作窗口源码 功能介绍: 多线程是一种提高程序运行效率和性能的常用技术。 随着我们学习工作的深入,在编程中或多或少会涉及到 需要多线程的情况。多数时候,我们的操作模式是后台 线程中处理数据,计算...

    VC++多线程下内存操作的优化

    VC++多线程下内存操作的优化

    VB多线程操作

    安全的多线程操作,是VB多线程的不错的选择

    多线程使用操作大全

    多线程操作多线程操作

    c# 委托访问listbox多线程操作

    此文档提供了一个委托方法,来控制访问listbox,用于多线程操作。c#

    易语言多线程操作模块

    易语言多线程操作模块源码,多线程操作模块,时间_现行时间,互斥锁创建,互斥锁销毁,互斥锁锁定,互斥锁解锁,互斥锁异步锁定,线程启动,线程启动多参,线程创建扩展,线程销毁,线程退出,线程等待,线程强制结束,线程键创建,...

    VC++ 多线程文件读写操作实例

    VC++环境下编写的一个多线程文件读写操作的实例。简单的利用互斥变量、原子操作等技术实现多线程读写文件时的互斥,从而确保文件在操作过程中不会被破坏。例子并没有使用很多复杂的逻辑。适合刚接触多线程编程的新手...

Global site tag (gtag.js) - Google Analytics