ConcurrentHashMap
Hash
散列,哈希:把任意长度的输入通过一种算法(散列),变换成固定长度的输出,这个输出值就是散列值;属于压缩映射,容易产生哈希冲突;
常见算法
直接取余,
md4、md5、sha 哈希算法,摘要
冲突解决办法:
- 开放寻址
- 再散列
- 链地址法
HashMap线程为什么不安全
put操作会引起死循环,hashMap里的entry链表产生环形的数据结构,entry的next 永远不为null;
https://www.cnblogs.com/wfq9330/p/9023892.html 这是jdk1.8以前的说法
1.8以后是否安全值得思考
ConcurrentHashMap为什么线程安全?
都是分段锁
jdk1.7及以前
16个 Segment ,每个Segment 里单独有 table
每个table里有 一个 hashEntry
每个 Segment 都是 一个 ReentrantLock 的子类;
get
定位Segment : key的 hashCode 进行再散列 值的高位 、取模
定位 table :key的 hashCode 进行在散列取值 取模
依次扫描链表,要么找到元素,要么返回null
key的哈希值在散列算出属于哪个Segment ,然后在该table里 循环entry获取 key
put
初始化的时候只会初始化第一个 Segment
put的时候,会调用ensureSegment方法,判断当前key定位的Segment存在不存在,不存在就new
存在的话就找到所在的位,看key是否存在,存在就覆盖,不存在就追加在链表上
Segment 加锁,
扩容:
Segment不扩容,扩容的是 table 数组 * 2
table的数组 HashEntry下标index ( 4 -> 8), index 要么不变,要么 index + 4;
size 的时候进行两次不加锁的统计,一致直接返回结果,不一致则加锁统计
弱一致性,
jdk1.8及以后
与1.7相比的重大变化
取消了 Segment 数组,直接用 table 保存数据,锁的粒度更小,减少发生冲突的概率
链表+红黑树 ,纯链表的形式,时间复杂度 O(n),红黑树 :O(logn),性能提升很大;
链表转红黑树 : table下元素个数大于等于 8 ; 个数小于等于6 时 红黑树转为链表
高16位 与 哈希值异或操作
Node 存放 key ,value
sizeCtl:
负数:表示进行初始化或者扩容,-1 表示正在初始化 -n 表示有n-1 个线程正在进行扩容
正数:0 表示还没有被初始化。 >0 初始化 或者 下一次扩容的阈值
TreeNode
TreeBin 实际放在table数组中的,代表了这个红黑树的根,
//Node 存放 key ,value
static class Node<K,V> implements Map.Entry<K,V> {
//有以下元素
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return val; }
public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
public final String toString(){ return key + "=" + val; }
public final V setValue(V value) {
throw new UnsupportedOperationException();
}
public final boolean equals(Object o) {
Object k, v, u; Map.Entry<?,?> e;
return ((o instanceof Map.Entry) &&
(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
(v = e.getValue()) != null &&
(k == key || k.equals(key)) &&
(v == (u = val) || v.equals(u)));
}
/**
* Virtualized support for map.get(); overridden in subclasses.
*/
Node<K,V> find(int h, Object k) {
Node<K,V> e = this;
if (k != null) {
do {
K ek;
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
} while ((e = e.next) != null);
}
return null;
}
}
put
// put 方法里 对数组进行初始化
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
//循环设置 sizeCtl
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
//数组 初始化
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);// 0.75N
}
} finally {
//控制 是否需要扩容
sizeCtl = sc;
}
break;
}
}
return tab;
}
-----------------------------------------------------------------------------------------
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
// key 的哈希值再散列
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// table 数组为 null 时,直接把key 放在数组里面,作为数组元素的一部分
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
//红黑树插入数据
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
//如果 链表长度 大于阈值 ,
if (binCount >= TREEIFY_THRESHOLD)
//由链表转为树
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
扩容操作
*2 的好处 : 可以快速定位和减少重排次数
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
// 当当前容量大于 扩容阈值
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
//实际扩容方法 翻倍的形式
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
//控制最大容量
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n;
}
int nextn = nextTab.length;
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
boolean advance = true;
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n; // recheck before commit
}
}
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
else {
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
if (fh >= 0) {
int runBit = fh & n;
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
if ((ph & n) == 0)
ln = new Node<K,V>(ph, pk, pv, ln);
else
hn = new Node<K,V>(ph, pk, pv, hn);
}
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
else if (f instanceof TreeBin) {
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> lo = null, loTail = null;
TreeNode<K,V> hi = null, hiTail = null;
int lc = 0, hc = 0;
for (Node<K,V> e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode<K,V> p = new TreeNode<K,V>
(h, e.key, e.val, null, null);
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
}
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
(hc != 0) ? new TreeBin<K,V>(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
(lc != 0) ? new TreeBin<K,V>(hi) : t;
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
}
}
}
}
}
帮助扩容
与hashMap的不同
帮助扩容,并发扩容的机制
当线程进来put ,发现有别的线程在扩容,这个线程会帮助扩容,扩容完成以后,再put
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
get
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
//table 这个数组中是我指定的key 直接返回
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
//在红黑树中寻找
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
//在链表中寻找
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
size
弱一致性,因为 CounterCell 里的数据 可能不是最新的;
所以是一个大概数量,不是一个精确数量;
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
final long sumCount() {
//counterCells 是 还没来得及统计进 baseCount 的数量
CounterCell[] as = counterCells; CounterCell a;
//baseCount 当前数量
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
更多的并发容器
ConcurrentSkipListMap 、 ConcurrentSkipListSet
TreeMap 和 TrssSet 有序的容器,这两种容器的并发版本;
了解什么是SkipList
跳表 、 SkipList
以空间换时间、相当于索引
再插入的时候取一个随机数,如果满足条件 就把当前插入元素作为索引;
概率数据结构
实现上比红黑树简单,但是查询效率非常接近红黑树
ConcurrentLinkedQueue
LinkedList 的 并发版本
无界非阻塞队列,底层是个链表,遵循先进先出原则;
add ,offer 方法 将元素插入到尾部,
peek(拿头部的数据,但是不移除) 、poll(拿头部的数据,并移除)
写时复制容器
CopyOnWriteArrayList 、 CopyOnWriteArraySet
读写分离的一种思想
往一个容器添加元素的时候,先把当前容器拷贝出来,往新容器里添加元素
再将原来容器的引用指向新的容器;
只能保证最终一致性,不能保证实时一致性;
适合读多写少的并发场景,白名单/黑名单
内存占用,
阻塞队列
概念,生产者消费者模式
当队列满的时候,插入元素的线程被阻塞,直到队列不满。
队列为空的时候,获取元素的线程就会被阻塞,直到队列不空
生产者消费者模式,能力不匹配的问题,加一个容器解决生产者和消费者直接的耦合;
常用方法
方法 | 抛出异常 | 返回值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入方法 | add | offer | put | offer(time) |
移除方法 | remove | poll | take | pool(time) |
检查方法 | element | peek | N/A | N/A |
常用阻塞队列
BlockingQueue
ArrayBlockingQueue 一个由数组结构组成的有界阻塞队列
按照先进先出原则排序、要求设定初始大小
LinkedBlockingQueue 一个由链表结构组成的有界阻塞队列
要找先进先出原则,可以不设定初始大小,默认 Integer.MAX_Value
区别:
锁上:ArrayBlockingQueue 只有一个锁,LinkedBlockingQueue 用了两个锁,
实现上 : ArrayBlockingQueue 直接插入元素,LinkedBlockingQueue 需要转换
PriorityBlockingQueue 一个支持优先级排序的无界阻塞队列
默认情况下,按照自然顺序,要么实现 compareTo()方法,指定构造参数Comparetor();
DelayQueue 一个使用优先级队列实现的无界阻塞队列
支持延时获取元素的阻塞队列,元素必须要实现 Delayed 接口,
实现自己的缓冲系统;订单到期,显示支付
SynchronousQueue 一个不存储元素的阻塞队列
每一个put操作都要等待一个take操作
LinkedTransferQueue 一个由链表结构组成的无界阻塞队列
transfer( ) ,看有没有消费者在等待,如果有,直接传给消费者,否则,放入队列; 必须要消费者消费了以后才会返回(哪怕是放入了队列);
tryTransfer( ) 无论消费者是否接收,都会立即返回;
LinkedBlockingDeque 一个由链表组成的双向阻塞队列
可以从队列的头和尾都可以插入和移除元素,
实现工作密取,方法名带了first 从头存取,带了last 存尾拿取
add = addLast , remove = removeFirst, take = takeFirst
尽量使用有界的,否则容易被撑爆
DelayQueue代码示例
/**
* 存放到元素的队列
*
* @param <T>
*/
public class ItemVo<T> implements Delayed {
//到期时长 单位 ms
private long activeTime;
private T date;
public ItemVo(long activeTime, T date) {
//将传入的时长转换为超时的时刻
this.activeTime = TimeUnit.NANOSECONDS.convert(activeTime, TimeUnit.MILLISECONDS) + System.nanoTime();
this.date = date;
}
public long getActiveTime() {
return activeTime;
}
public T getDate() {
return date;
}
/**
* 返回元素的剩余时间
*
* @param unit
* @return
*/
@Override
public long getDelay(TimeUnit unit) {
long d = unit.convert(this.activeTime - System.nanoTime(), TimeUnit.NANOSECONDS);
return d;
}
/**
* 按照剩余时间排序
*
* @param o
* @return
*/
@Override
public int compareTo(Delayed o) {
long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
return d == 0 ? 0 : (d > 0 ? 1 : -1);
}
}
/**
* 订单实体类
*/
public class Order {
private final String orderNo;
private final double orderMoney;
public Order(String orderNo, double orderMoney) {
this.orderNo = orderNo;
this.orderMoney = orderMoney;
}
public String getOrderNo() {
return orderNo;
}
public double getorderMoney() {
return orderMoney;
}
}
/**
* 添加订单
*/
public class PutOrder implements Runnable {
private DelayQueue<ItemVo<Order>> queue;
public PutOrder(DelayQueue<ItemVo<Order>> queue) {
super();
this.queue = queue;
}
@Override
public void run() {
// 5 s 到期
Order order = new Order("tb",12.32);
ItemVo<Order> itemVo = new ItemVo<Order>(5000,order);
queue.offer(itemVo);
System.out.println("订单 5s 后到期" + order.getOrderNo());
//8s到期
Order order1 = new Order("jd",12.34);
ItemVo<Order> itemVo1 = new ItemVo<Order>(8000,order1);
queue.offer(itemVo1);
System.out.println("订单 8s 后到期" + order1.getOrderNo());
}
}
/**
* 取订单
*/
public class FetchOrder implements Runnable {
private DelayQueue<ItemVo<Order>> queue;
public FetchOrder(DelayQueue<ItemVo<Order>> queue) {
super();
this.queue = queue;
}
@Override
public void run() {
while (true){
try {
ItemVo<Order> itemVo = queue.take();
Order order = (Order) itemVo.getDate();
System.out.println("取出 " + order.getOrderNo());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
DelayQueue<ItemVo<Order>> delayQueue = new DelayQueue<>();
new Thread(new PutOrder(delayQueue)).start();
new Thread(new FetchOrder(delayQueue)).start();
for (int i = 0; i < 15; i++) {
Thread.sleep(500);
System.out.println(i * 500);
}
}
}
阻塞队列的实现原理