android 多线程 — 并发集合 CopyOnWriteArrayList、ConcurrentHashMap

本篇我们来看看集合在多线程环境下的新变化

在多线程中我们需要考虑任何数据对象的同步问题,使用率很高的集合类型对象也不例外,更是重中之重。集合是存储容器的,多线程环境下是线程访问的重点,那么就可能造成并发的问题。java 传统的集合中只有 Vector 、HashTable、StringBuffer 是线程安全的,但只能做到 synchronized 的效果,限制集合在同一时间只能游一个线程来操作,对于其他的对象到是没问题,但是上面说过了,集合是访问重点,是会频繁使用的,这样一次只能游一个线程操作,其他的都得在后面等着(阻塞),在并发量大的情况下会产生巨大的性能问题。那么有什么办法吗

当然有啦,要不我怎么会写这篇文章啊,JAVA 提供了可以在多线程环境下使用的新的集合容器,既能保证数据同步,也能提高访问效率

这几个新的集合容器就是:

  • CopyOnWriteArrayList
    读写分离的 list 集合
  • ConcurrentHashMap
    多锁结构的 map 集合

我找到的比较好的描述:

JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能。因为同步容器将所有对容器状态的访问都

串行化了,这样保证了线程的安全性,所以这种方法的代价就是严重降低了并发性,当多个线程竞争容器时,吞吐量严重降低。因此Java5.0开

始针对多线程并发访问设计,提供了并发性能较好的并发容器,引入了java.util.concurrent包。与Vector和Hashtable、

Collections.synchronizedXxx()同步容器等相比,util.concurrent中引入的并发容器主要解决了两个问题:

  1. 根据具体场景进行设计,尽量避免synchronized,提供并发性。
  2. 定义了一些并发安全的复合操作,并且保证并发环境下的迭代操作不会出错。

CopyOnWriteArrayList


CopyOnWriteArrayList 名字看着可能会有点懵逼,见名知意的话是个啥意思。其他很简单的,就是读写分离的 list ,多线程环境下对集合的读操作是不加锁的,允许多个线程同时读取集合内容;对集合写的操作是加锁的。

这样把读和写操作分离,一个线程同步,一个线程不同步,根据线程安全性区分操作,无疑可以大大提高不影响线程安全操作的多线程效率

大家想啊,读操作只是取数据,不会对数据造成影响,天然的是可以允许并发的

写操作是要改变数据的,是会别的线程造成影响的,肯定是要保证线程安全的。

CopyOnWriteArrayList 读写分离的做法体现了多线程优化的一个思路,把关乎线程安全与否的操作分离,会大大提供不影响线程安全的操作的效率。

这里解释下 CopyOnWriteArrayList 的原理,为啥读和写可以分离。CopyOnWriteArrayList 在写操作时,先把集合数据 copy 于一份出来,然后在这个副本上对集合进行操作,计算结速后再把用副本数据覆盖原始数据,写操作是线程安全的,是同步的,同一时刻只能有一个线程操作。在写操作的同时因为我们不直接修改原始数据,而是用的副本,对原始数据没有任何影响,所以读的操作可以不受写操作的干扰,可以并发操作。

这让我想起 realm 数据库来了,realm 就是在每一个线程都存在一个数据库的副本,我们在这个线程中操作的是副本,然后 realm 自己决定何时同步副本数据到主数据库中。也是用的是副本的套路来支持多线程并发的。任何线程的对数据的操作都不影响别的线程

我们来看下 CopyOnWriteArrayList 的写入方法就更清楚了,源码很简单的,不要有压力

    public boolean add(E e) {
        synchronized (lock) {
            // 取出数据
            Object[] elements = getArray();
            int len = elements.length;
            // 创建副本
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            // 用副本复杂原始数据
            setArray(newElements);
            return true;
        }
    }

CopyOnWrite的缺点

  • CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。

  • 内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。

  • 针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。

  • 数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

ConcurrentHashMap


HashMap 是根据散列值分段存储的,同步 Map 在同步的时候锁住了所有的段,而ConcurrentHashMap 给每个散列值分段都加了一把锁,这样 ConcurrentHashMap 能允许对不同散列值分段的并发操作,当然同散列值分段的操作还是只能有一个线程的,但是这样能大大提高了并发性能

ConcurrentHashMap为了提高本身的并发能力,在内部采用了一个叫做Segment的结构,一个Segment其实就是一个类Hash Table的结构,Segment内部维护了一个链表数组,我们用下面这一幅图来看下ConcurrentHashMap的内部结构:


nuEZ0.png

从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上),所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。

另外 ConcurrentHashMap 也是读写分离的,get() 是不加锁的,put 加锁

最后


阻塞队列其实也算是并发容器的,但是这个我想和线程池一起说。并发集合容器就说到这里了,我也是做 android 的,对于多线程也是个门外汉,没啥实战经验,对基础理解页很浅薄。这里我简单介绍了2种并发容器的概念和原理,剩下的使用其实和集合没却别,更多的内容请大家自己去找更详细的资料吧

参考资料


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,220评论 11 349
  •   一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺...
    OmaiMoon阅读 1,664评论 0 12
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • 这次有许多的实践活动有陶泥,有魔幻厨房,有安全体验的课程……我在综合实践中选择了乐享3D。 课程开始了,老师问...
    馨蝶希儿阅读 198评论 0 3