线程安全的list+map


一、Vector和SynchronizedList

1.1回顾线程安全的Vector和SynchronizedList

我们知道ArrayList是用于替代Vector的,Vector是线程安全的容器。因为它几乎在每个方法声明处都加了synchronized关键字来使容器安全。


如果使用Collections.synchronizedList(new ArrayList())来使ArrayList变成是线程安全的话,也是几乎都是每个方法都加上synchronized关键字的,只不过它不是加在方法的声明处,而是方法的内部



二、CopyOnWriteArrayList(Set)介绍

一般来说,我们会认为:CopyOnWriteArrayList是同步List的替代品,CopyOnWriteArraySet是同步Set的替代品。

无论是Hashtable-->ConcurrentHashMap,还是说Vector-->CopyOnWriteArrayList。JUC下支持并发的容器与老一代的线程安全类相比,总结起来就是加锁粒度的问题

Hashtable、Vector加锁的粒度大(直接在方法声明处使用synchronized)

ConcurrentHashMap、CopyOnWriteArrayList加锁粒度小(用各种的方式来实现线程安全,比如我们知道的ConcurrentHashMap用了cas锁、volatile等方式来实现线程安全..)

JUC下的线程安全容器在遍历的时候不会抛出ConcurrentModificationException异常

所以一般来说,我们都会使用JUC包下给我们提供的线程安全容器,而不是使用老一代的线程安全容器。

下面我们来看看CopyOnWriteArrayList是怎么实现的,为什么使用迭代器遍历的时候就不用额外加锁,也不会抛出ConcurrentModificationException异常。

2.1CopyOnWriteArrayList实现原理

我们还是先来回顾一下COW:

如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作时可以共享同一份资源

参考自维基百科:zh.wikipedia.org/wiki/%E5%AF…

概括一下CopyOnWriteArrayList源码注释介绍了什么:

(1)0CopyOnWriteArrayList是线程安全容器(相对于ArrayList),底层通过复制数组的方式来实现。

(2)CopyOnWriteArrayList在遍历的使用不会抛出ConcurrentModificationException异常,并且遍历的时候就不用额外加锁

(3)元素可以为null

2.1.1看一下CopyOnWriteArrayList基本的结构


看起来挺简单的,CopyOnWriteArrayList底层就是数组,加锁就交由ReentrantLock来完成。

2.1.2常见方法的实现

根据上面的分析我们知道如果遍历Vector/SynchronizedList是需要自己手动加锁的。

CopyOnWriteArrayList使用迭代器遍历时不需要显示加锁,看看add()、clear()、remove()与get()方法的实现可能就有点眉目了。

首先我们可以看看add()方法

通过代码我们可以知道:在添加的时候就上锁,并复制一个新数组,增加操作在新数组上完成,将array指向到新数组中,最后解锁。

再来看看size()方法:

再来看看get()方法:

那再来看看set()方法

了。

总结:

(1)在修改时,复制出一个新数组,修改的操作在新数组中完成,最后将新数组交由array变量指向

(2)写加锁,读不加锁

2.1.3剖析为什么遍历时不用调用者显式加锁

常用的方法实现我们已经基本了解了,但还是不知道为啥能够在容器遍历的时候对其进行修改而不抛出异常。所以,来看一下他的迭代器吧:

到这里,我们应该就可以想明白了!CopyOnWriteArrayList在使用迭代器遍历的时候,操作的都是原数组

2.1.4CopyOnWriteArrayList缺点

看了上面的实现源码,我们应该也大概能分析出CopyOnWriteArrayList的缺点了。

内存占用:如果CopyOnWriteArrayList经常要增删改里面的数据,经常要执行add()、set()、remove()的话,那是比较耗费内存的。

因为我们知道每次add()、set()、remove()这些增删改操作都要复制一个数组出来。

数据一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性

从上面的例子也可以看出来,比如线程A在迭代CopyOnWriteArrayList容器的数据。线程B在线程A迭代的间隙中将CopyOnWriteArrayList部分的数据修改了(已经调用setArray()了)。但是线程A迭代出来的是原有的数据。

2.1.5CopyOnWriteSet

CopyOnWriteArraySet的原理就是CopyOnWriteArrayList。


ConcurrentHashMap

数据结构

JDK 1.8中ConcurrentHashMap抛弃了分段锁技术的实现,直接采用CAS + synchronized保证并发更新的安全性,底层采用数组+链表+红黑树的存储结构。其包含核心静态内部类 Node。

首先通过一张图来看下数据结构吧:

数据结构图

说明:数据结构采用数组 + 链表 + 红黑树的方式实现。当链表中(bucket)的节点个数超过8个时,会转换成红黑树的数据结构存储,这样设计的目的是为了减少同一个链表冲突过大情况下的读取效率。

Java8中主要做了如下优化:

1.将Segment抛弃掉了,直接采用Node(继承自Map.Entry)作为table元素。

2.修改时,不再采用ReentrantLock加锁,直接用内置synchronized加锁,java8的内置锁比之前版本优化了很多,相较ReentrantLock,性能不并差。

3.size方法优化,增加了CounterCell内部类,用于并行计算每个bucket的元素数量。

内部类和继承关系

Java8中ConcurrentHashMap增加了很多内部类来支持一些操作和优化性能。下面介绍几个核心的内部类。

ConcurrentHashMap几个核心内部类关系图

(1)Node类:存放元素的key,value,hash值,next下一个链表节点的引用。用于bucket为链表时。

(2)TreeBin:内部属性有root,first节点,以及root节点的锁状态变量lockState,这是一个读写锁的状态。用于存放红黑树的root节点,并用读写锁lockState控制在写操作即将要调整树结构前,先让读线程完成读操作。从链表结构调整为红黑树时,table中索引下标存储的即为TreeBin。

(3)TreeNode:红黑树的节点,存放了父节点,左子节点,右子节点的引用,以及红黑节点标识。

(4)ForwardingNode:在调用transfer()方法期间,插入bucket头部的节点,主要用来标识在扩容时元素的移动状态,即是否在扩容时还有并发的插入节点,并保证该节点也能够移动到扩容后的表中。

(5)ReservationNode:占位节点,不存储任何信息,无实际用处,仅用于computeIfAbsent和compute方法中。

重要属性介绍


以上的一些属性,在初始化,扩容,链表转红黑树等方法中用到。属性众多,sizeCtl,counterCells都比较重要。

sizeCtl:即作为table初始化状态的标识,也用作扩容时的线程数标识,还用作初始和扩容后table的容量标识,用处很多,不同状态值代表的含义如下:

1、 -1:标识table正在初始化

2、- N:标识table正在进行扩容,并且有N - 1个线程一起在进行扩容

3、正数:初始化table的大小,如果值大于初始容量大小,则表示扩容后的table大小。

链接:https://juejin.im/post/5be23e6ef265da6135720d61 链接:https://www.jianshu.com/p/85d158455861

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

推荐阅读更多精彩内容