多线程----ConcurrentHashMap

学习ConcurrentHashMap需要达到以下三点:

一、比较HashMap为什么不是线程安全的,及HashTable是如何实现的安全的,并且HashTable有什么问题?

二、深入理解CHM各项并发优化的原理。

三、掌握锁优化的方法。

一、比较HashMap为什么不是线程安全的,及HashTable是如何实现的安全的,并且HashTable有什么问题?

1、HashTable的问题(很暴力):

  • 大锁:直接对HashTable对象加锁。
  • 长锁:直接对方法加锁。
  • 读写锁共用:只有一把锁,从头锁到尾。

CHM对于HashTable的问题进行的优化:

  • 小锁:分段锁(5~7),桶节点锁(8)
  • 短锁:先尝试获取,失败再加锁
  • 分离读写锁:读失败再加锁(5~7),volatile读CAS写(7~8)
二、深入理解CHM各项并发优化的原理。
1、CHM的并发优化历程:
JDK1.5:分段锁,必要时加锁。

如下图对Key进行hash后,高位用来找segment,低位用来找table。
在这里插入图片描述

JDK1.6: 优化二次Hash算法。

因为JDK1.5时的hash算法会导致hashcode的高位不均匀分布,对于30000以下的整数key, hash出来后大部分集中在第16个segment中,对于50万以下的整数key,hash出来后大部分集中在第14,15个segment中,这样就退化为了HashTable。而JDK1.6时的hash算法实现高低位的均匀分布。
在这里插入图片描述

JDK1.7: 段懒加载(段不一开始就实例化,而是需要时实例化),使用volatile & cas 来避免加锁。

JDK1.7之前的16个segment在一开始全都实例化出来,但是JDK1.7之后需要哪个new哪个。因为segment是需要的时候创建,所以有可能多个线程访问的时候有可见性问题,所以JDK1.7大量的使用了volatile来保证segment的线程安全。

JDK1.8: 摒弃段,基于HashMap原理的并发实现。不必加锁的地方尽量使用volatile,必须加锁的地方如写入,尽量小范围的加锁。

jdk8开始,没有了段,所以只在hash的entry上加锁,缩小了范围。

为何放弃分段锁?
段Segment继承了重入锁ReentrantLock,有了锁的功能,每个锁控制的是一段,当每个Segment越来越大时,锁的粒度就变得有些大了。

2、各版本计数改进:

jdk5~7基于段元素个数求和,二次不同就加锁再求一次。
jkd8没有了段,引入CounterCell, 本质上也是分段计数。

3、CHM是弱一致性的:
  1. 添加元素后不一定能马上读到
  2. 清空后仍可能会有元素
  3. 遍历之前的段元素的变化会读到
  4. 遍历之后的段元素变化读不到
  5. 遍历时元素发生变化不抛异常
三、掌握锁优化的方法。

我们在多线程程序设计过程中可以借鉴什么呢?

  1. 长锁不如短锁,尽量只锁必要部分
  2. 大锁不如小锁,尽可能对加锁的对象进行拆分
  3. 公锁不如私锁,尽可能将锁的逻辑放在私有代码里
  4. 嵌套锁不如扁平锁,尽可能在代码设计时避免嵌套锁
  5. 分离读写锁,尽可能将读锁和写锁分离(如果大部分时间在读,只有少部分时间在写,那么给写加一个大锁,读加volatile或者不加锁)
  6. 粗化高频锁,尽可能合并处理频繁的短锁
  7. 消除无用锁,尽可能不加锁,或用volatile代替(可以保证原子性或者可见性的话)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值