ConcurrentHashMap和锁

JDK1.7的分段锁

数据结构:数组(大数组 Segment 和小数组 HashEntry)+链表

ConcurrentHashMap
Segment1
Segment2
Segment...
HashEntry1.1
HashEntry1.2

将整个哈希表划分为多个 Segment(段),每个 Segment 是一个独立的哈希表(类似 HashTable),默认16个Segment。

访问某个键值对时,先根据哈希值高位确定对应的 Segment,再对该 Segment 加锁。其他 Segment 不受影响,允许并发操作。

JDK1.8的volatile+CAS + synchronized

数据结构:数组+链表+红黑数

与 HashMap 类似,链表长度超过阈值(默认 8)转红黑树,低于阈值(默认 6)转回链表

添加元素putVal时,判断容器是否为空

1.空:数组尚未初始化,用volatile 修饰的sizeCtl变量作为初始化状态标志,通过加 CAS 操作修改sizeCtl的值,只有第一个将sizeCtl从0改为-1的线程能执行初始化,其他线程让出CPU。

if (tab == null || (n = tab.length) == 0)
    tab = initTable(); // 初始化数组

U.compareAndSwapInt(this, SIZECTL, sc, -1)

2.不为空:则根据存储的元素计算目标桶是否为空。

​ 2.1桶为空:则利用 CAS 设置该节点,tabAt()保证读取最新值,casTabAt()原子性插入新节点。

if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
        break;
}

​ 2.2不为空:已有链表或红黑树,则使用 synchronized对桶头节点加锁,然后,遍历桶中的数据,插入新节点或更新现有节点,最后判断是否需要转为红黑树。

volatile:

保证了变量的可见性,即一个线程修改了volatile变量,其他线程能立即看到变化

CAS:

无锁算法,当初始化或者插入节点时,通过CAS来确保只有一个线程能成功修改状态,比如初始化数组或者在某个桶中插入第一个节点。如果多个线程同时尝试,CAS会保证只有一个成功,其他线程则重试或采取其他策略。

synchronized

用于同步代码块,确保同一时间只有一个线程执行该代码块。当桶中已经有节点时,ConcurrentHashMap会使用synchronized锁定该桶的头节点,然后进行链表或红黑树的插入操作。这样可以避免多个线程同时修改同一个桶,保证数据一致性。

请添加图片描述

可重入锁

同一个线程可以多次获取同一把锁,而不会导致死锁。即同一个线程可以重复进入被自己持有的锁保护的代码区域。

想象一个场景:

  • 你有一个保险箱(共享资源),你(线程)用钥匙(锁)打开了它(获取锁)。
  • 在保险箱里,你发现一张纸条写着:“打开第二个抽屉需要同一把钥匙”。
  • 可重入锁:你直接用已有的钥匙打开第二个抽屉(无需重新申请钥匙)。
  • 不可重入锁:你被要求归还钥匙后才能再次申请,但钥匙在你手里,导致死锁(自己卡住自己)。

可重入锁解决了 “线程因重复获取自己已持有的锁而阻塞自己” 的问题。

ReentrantLock和synchronized都是可重入的。

ReentrantLock

显示可重入互斥锁,可中断锁获取、公平锁策略、条件变量(Condition)等

特性说明
可重入性同一线程可重复获取锁(通过计数器实现,避免自死锁)
公平性可选支持公平锁(先申请先获取)和非公平锁(允许插队,默认策略)
可中断锁获取线程在等待锁时可响应中断(lockInterruptibly()
超时尝试获取锁支持设定超时时间尝试获取锁(tryLock(long timeout, TimeUnit unit)
条件变量通过 newCondition() 创建多个等待队列,实现精细的线程唤醒控制
公平锁和非公平锁

公平锁:

按照线程请求锁的顺序来分配锁,也就是先到先得

非公平锁:

允许插队,后来的线程有可能先获取到锁

对比维度公平锁非公平锁
公平性严格按请求顺序分配锁允许插队,新线程可能优先获得锁
吞吐量较低(维护队列需要额外开销)较高(减少线程切换,允许插队)
饥饿问题不会发生线程饥饿可能发生线程饥饿(某些线程长期得不到锁)
适用场景需要严格顺序的场景(如支付系统、票务系统)大多数高并发场景(如缓存访问、计数器)
实现复杂度高(需维护队列)低(直接竞争)

在业务允许的情况下,优先使用非公平锁以提升系统吞吐量。

悲观锁和乐观锁

悲观锁先加锁,再操作

认为并发操作一定会发生冲突,因此每次访问数据时都会加锁,比如synchronized和ReentrantLock

举个例子:出门时锁门(默认有小偷)

乐观锁先操作,提交时再检查冲突

认为并发操作很少发生冲突,只在提交操作时检查是否冲突,比如CAS操作,数据库的乐观锁和Java中的Atomic类。

举个例子:

1.购物车结算时才检查库存(默认没人抢购)

2.或者在网上订票,系统显示还有1个座位,你点击预订,系统会先让你填写信息,然后提交的时候检查是否还有座位。如果有,预订成功;如果没有,提示你重新选择

在这里插入图片描述
正在学习中,如有错误,大家一定要指出来!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LDM>W<

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值