关于容器类

在这里说一下,线程安全和线程不安全的容器类。请带着质疑的角度看待这篇文章,感恩。

前提


什么是线程安全?

通俗的说,在多线程中,在执行一段代码上,仍然按照我们原先设想的逻辑运行,产生我们意向到的结果。

学术的说,在一个线程调用共享资源的时候,什么时候让其他线程,或者怎么样让其他线程看到这个资源的变化

java内存模型如何应对上述问题?

happend-before ,数据依赖性 ,as-if-serial ,  内存屏障,----这些都是禁止某些指令重排

happend-before ,数据依赖性 ,as-if-serial 这三个都是说,禁止某些重排序,按照前趋图,执行。就是写了才能读,开始了才能结束,有一个逻辑上的先后关系。当然happend-before有个更重要的一点

A hb B

B hb C

结果 : A hb C

内存屏障:禁止某些重排序 + 缓存和主存之间数据的一致性问题。

如何保证线程安全,

锁(synchronized / lock ) , CAS  , 写时复制,


正题


关于数据结构,分为逻辑上的和真实存储在计算机中的数据的结构。也可以分为真实的结构(数组,链表)具体的程序员实现的抽象结构(栈,队列 ,堆,图,树,散列表).

java中常见的,我下面要说的数据结构:list , set , map

1.关于list :

有序(存储的/插入的顺序),意思就是排队的内个意思,每个人都拿着爱的号码牌,

可以重复

删除,查找的时候要比较,数据类型为引用数据类型时 , 类要重写equals()方法。

单线程:

ArrayList和LinkedList的差别?

看过源码都知道:ArrayList 用数组实现,查询效率高,插入删除效率低, LinkedList是用双向链表实现的,插入删除效率高 ,查询效率低

这些单线程的都是强一致性,有一个字段modCount (对象被修改次数)这个字段作用:在用迭代器的遍历中,这个时候其他线程修改了数据的结构(增/删),就会报ConcurrentModificationException异常。

多线程安全:

1.Vector.Class  , 在每个方法上加synchronized关键字,加锁实现线程安全。

2.Collections.synchronizedList(....) ,也是通过加synchronized锁来实现线程安全

3.juc包下CopyOnWriteArrayList.Class 数组实现,适用在读多,写少的场合中。

- 使用 ReentrantLockvolatile  和 写时复制 ,三个方面解决线程安全问题

- volatile : 解决数组的内存可见性

-RentrantLock ,锁住的是:修改容器内的数据的时候的只允许一个线程修改。

-写时复制:保证如果有线程正在修改容器内的数据,这个时候有线程想读,读取容器内的数据,修改的是复制的版本,修改之后把数据在刷新在容器内,因为这个容器内的数据是volatile, 保证多线程中的内存可见性。



2.关于Map

这里先说Map,因为大部分Set中数据的存储在对应Map中的Key中。

容器内的数据类型,如果是引用数据类型,要重写hashCode()方法和equals()方法,因为添加对象步骤如下;

首先调用hashCode()方法,(此计算的对象哈希值决定了Set中的存储位置):若此位置之前没有对象存储,则这个对象,直接存储到此位置上;若有存储对象,通过equals方法,比较这两个对象是否相同,如果相同,后一个对象就不能再添加,万一返回false呢,则都存储在"同一个位置"。


单线程:

1.HashMap (数组 + 链表+红黑树 ,null ok)

问题:HashMap  get() / put()的工作原理?

存储过程:

0.hash(key.hashCode())  充分使用key的hashCode值的前16位和后16位 “&”操作 得到一个 int  hash值

1.判断table是否为null,是就扩容

2.这个hash值 & (数组Size -1 ) , 得出找到要放在桶上的索引,如果这个位置上没值,就保存key -value,

3.如果索引位置上有值,再equalse()判断是否相等,如果相等,就覆盖old value

4. 如果索引位置上的值和插入的值不相等,就检查这个节点是不是treeNode,如果是就红黑树插入key-value ,这里依然要equals(),如果相等就 覆盖 old value

5. 如果不是红黑树,那么遍历他的链表,遍历的过程中做两件事  1.检查是否遍历到了链表的尾端,如果是就插入到尾端检查这个时候链表的长度是否是8,是的话 ,(判断数组的length如果大于64,就把链表转化为红黑树,小于64就扩容)    2.equals()于当前链上结点是否相等,相等就跳出遍历,覆盖old valueB

6.覆盖了old value会返回old value , 没有覆盖就modCount +1 ,检查是否需要扩容

get() 过程:

1. hash(key.hashCode()) & (table.length -1) 找到索引,桶的位置

2.索引上没有值,返回null , 桶上有值equals() 相等,返回;

3.不等就看这个节点是不是treeNode,是就查找equals(),true返回值,false 就返回null

4.不是treeNode , 就查看这个有没有next个节点链,遍历,equals(),同上。

问题:HashMap中解决碰撞的方法?

首先知道几个数字 :

16 : capacity 默认数组的长度 , 不默认就2的幂次方,减少碰撞。

8:当值插入值后链表长度变成 8 就把链表变为红黑树,或者扩容

扩容相关参数

0.75:final  loadFactor 负载因子,当添加key-value之后 , 检查当前的容量 size 是否达到了threshold(capacity*loadFactor)阈值,达到了就扩容。至于为什么是0.75呢?javadoc上说,0.75满足泊松分布。

64: final,最小转变为红黑树的这个一个阈值: 在链表转红黑树中 , 如果map中table 的长度没有达到这个值,就会通过扩容解决冲突,并不会链表转红黑树

总结:这个如何解决冲突,就是在桶后面加了一个链表,为了解决链表过长查找效率变为O(n),链表过长变为红黑树查找效率变为O(logN)

问题:HashMap的扩容?

扩容就是变成原来table.length的两倍,这样的好处,关于整体结构上的动荡,这个值,要不就在 原来的索引上,要不就在原来的索引+原来table.length处。多线程扩容会产生循环链表,引起死锁,让Cpu的使用率变成100%

继承HashMap的LinkedHashMap:

就是在HashMap的基础上,加了双向链表,维护了一个顺序,默认是插入的顺序,可以用过构造方法指定accessOrder为true, 然后重写LinkedHashMap中的removeEldestEntry方法  这样维护的是LRU(最近最久未使用淘汰)顺序。

问题:如何维护顺序问题?

LinkedHashMap 继承HashMap之后重写了三个钩子方法,用着三个方法来维护一个顺序问题

2.TreeMap(红黑树)

自定义key的排序  ,在数量级很大的情况下使用TreeMap。关于key为null的问题 , 可以put存储,如果get读的时候回报错NullPointerException异常,但是可以遍历,迭代器读取

多线程安全:

1.Hashtable : (不允许key为 null)加synchronized 关键字实现同步方法 .

2.Collections.synchronizedMap() /synchronizedSortedMap()

3.ConcurrentHashMap ( 数据类型和HashMap一样,不能为null ) : 

volatile table  , CAS方法一些状态值的维护或者插入  , synchronized(node),三个原子操作

问题:put() / get()的工作原理?HashMap有什么不一样呢 ?

put()

0.这里写了循环,只有插入成功才会返回。

1.判断当桶上是不是有值 ,CAS插入值,插入成功就返回,有值之后先判断是否有其他线程是否在扩容,是就帮助扩容,没有扩容就接着插入。

2.加锁的粒度不是整个这个方法,加锁是在node结点上,这个时候synchronized(当前这个桶),下面和HashMap一样,是链表就插在链表后面(这个之后要判断链表长度是否大于8,当前table.length是否大于64,是就转变为红黑树),是红黑树就按照红黑树插入。

3.插入之后判断是否要扩容

问题:关于扩容的问题?很难

在ConcurrentHashMap里,sizeCtl承担map扩容的问题,并增加了一点功能。

当sizeCtl=-1,代表正在初始化。

当sizeCtl=-N,代表有N-1个线程在扩容。

当sizeCtl=0,代表需要初始化。

以上三个值,都是状态位。

当sizeCtl>0,即扩容的阈值,也即总容量 * 0.75。

多线程扩容,每个线程都扩容自己分到的桶,扩容完成后把node结点变为ForwardingNode,通知其他线程这个桶已经扩容完毕,并且设置桶上hash值为 MOVED

问题:ConcurrentHashMap的弱一致性问题?

如果有线程遍历,这个时候有线程做修改数据结构的事情?

在使用迭代器时,如果修改的部分发生在还没有迭代的地方,就会显示出来,如果迭代在已经迭代的地方就不会显示出来

问题:ConcurrentHashMap并行度?

读完全并行。

写部分并行,因为synchronized的是桶上的node , 所以table的length有多少,并行度就是多少。

迭代器

3.ConcurrentSkipListMap :

ConcurrentHashMap是HashMap的 多线程版本,ConcurrentSkipListMap是TreeMap的多线程版本,对key有一个排序。

无阻塞,所有的操作都是并行的 , 跳表基于链表 , 这个结构查找类似二分查找。这个以后再写



3.关于Set

1.无序(存储位置 根据Hash值),他和list一样都继承Collection.Class

2.不可重复

单线程:

HashSet , TreeSet 这些类各自实现 HashMap , TreeMap,以其中的key保存自己的值,value是一个默认的值。

线程安全:

1.Collections.synchronizedSet

2.CopyOnWriteArraySet()

同CopyOnWriteArrayList一样, 使用volatile 数组 + lock +写时复制 ,实现线程安全。

3.ConcurrentSkipListSet ; 基于ConcurrentSkipListMap实现的

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

推荐阅读更多精彩内容