JUC(线程安全集合类)
线程安全集合类
遗留线程安全类
加锁
HashTable
Vector
修饰的安全集合
装饰器模式,传入不安全集合,内部加锁
SynchronizedMap
SynchronizedList
JUC安全集合
Blocking类
大部分实现基于锁,并提供用来阻塞的方法
CopyOnWrite类
修改时拷贝,开销相对较重
Concurren类
cas优化,采用多把锁提高并发度和吞吐量
ConcurrentHashMap
保证get和put的原子性:computeIfAbsent(key,(key)->value);
- JDK1.7HashMap死链
死链的问题是,线程一已经完成扩容,线程二拿着旧的引用(此时e还是1,但是next此时已经连上e了),将1放入新哈希表,将next放入新哈希表,next->1,然后又将1放入哈希表形成死链:1<->next
重要属性
sizeCtl:当第一次初始化变为-1(默认0),初始化后值为下次扩容的最大元素个数
Node<K,V>:链表节点
Node<K,V>[] table:数组
nextTable:扩容时新hash表
ForwardingNode<K,V>(头结点):此桶rehash过了,get应该去新数组
TreeNode<K,V>:红黑树节点(防止DDOS或者hash函数泄露链表过长,查询效率大幅下降)
TreeBin<K,V>:红黑树头结点(指向第一个红黑树节点)
如果遭遇DDOS或者hash函数泄露,我们也可以通过加盐的方式进行防御:
Java 内部,这种“盐”通常被称为 Seed(种子)。
实现原理:在系统启动时,随机生成一个 hash_seed。
计算逻辑:index = Hash(key + hash_seed) % table_size。
效果:即使攻击者知道哈希函数的算法(比如知道你用的是 Jenkins Hash),但因为他们不知道你服务器内存里随机生成的 seed 是什么,所以无法在本地预计算出能导致碰撞的 Key。这让攻击者构造碰撞数据的代价从“确定性”变成了“纯撞大运”。
常见方法
- tabAt(tab,i):获取node[]中第i个node
- casTabAt(tab,i,c,v):cas修改tab中第i个node值,c为旧值,v为新值
- setTabAt(tab,i,v):直接修改tab中第i个node值,v为新值
- 构造方法:ConcurrentHashMap(int initialCapacity,float loadFactor,int concurrencyLevel)
负载因子至少大于并发度 - get:get(Object key),代码中无锁
- put:put(K key,V value),默认新值覆盖旧值
jdk7(ConcurrentHashMap)
维护一个segment数组,每个segment对应一把锁
优点:如果多个线程访问不同的segment,实际是没有冲突的
缺点:segment数组默认大小为16,这个容量初始化指定后就不能改变,并且不是懒惰初始化
LinkedBlockingQueue原理
两把锁,同一时刻允许两个线程(一个生产者一个消费者)执行
ConcurrentLinkedQueue
两把锁,只是锁用的是cas
CopyOnWriteArrayList
CopyOnWriteArraySet是它的马甲
写入时拷贝思想
增删改操作会将底层数组拷贝一份,更改操作在新数组上执行,这时不影响其他线程的并发读,读写分离
适合读多写少场景
数据库的MVCC就是弱一致性
高并发和一致性是矛盾的
更多推荐




所有评论(0)