线程安全集合类

遗留线程安全类

加锁

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就是弱一致性
高并发和一致性是矛盾的

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐