java 同步工具类_Java 同步工具类

1 AQS

自旋锁,将竞争资源的线程自旋在一个标志位。

CLH队列,排列竞争资源的线程,每个线程自旋检查 pre 的标志位。当 head 释放锁时标志位改为 false,后继线程自旋发现后,替换掉 head 并获得锁。

AQS 改良 CLH队列,允许线程阻塞,子类重写少量代码就能实现 公平、非公平,互斥、共享,Condition 等功能。

state 同步变量,在不同的 Sync 实现中有不同的语义

head 指向 CLH队头,表示占有资源的线程

tail 指向 CLH队尾,表示最新的等待线程

exclusiveOwnerThread 记录占用 head 的线程

1.1 CAS 实现

Unsafe#compareAndSwap 都是 native 方法,有两种处理机制:

缓存锁定。在一个处理器的缓存中操作数据,并使其它处理器中的缓存失效

总线锁定。处理器通过总线交流,锁定总线让处理器中间对一个数据的操作串行

1.2 ABA 问题

在 CAS 更新过程中,线程1 读到的值是A,此时线程2把 A 改为 B 后又改回 A,线程1 回过神来发现值依然是A。

ABA 问题在大部分场景下都不影响并发的最终效果,但 Java 提供了 AtomicStampedReference,它要求传入 expectedStamp、newStamp,更新时不光检查值,还要检查当前的标志是否等于预期标志,全部相等的话才会更新。

2 Lock

ReentrantLock 使用 state 实现重入,默认非公平。

ReentrantReadWriteLock 持有1个读锁,1个写锁,默认同一个 NonfairSync 实现。

2.1 使用场景

雪花算法自增序列,使用 ReentrantLock 保证自增操作的原子性。

2.2 公平锁实现

非公平锁使用 NonfairSync 实现:

尝试加锁(CAS 修改 state)

尝试加锁,失败则把表示当前线程的 Node 入队 CLH

如果节点的 prev 是 head,自旋加锁,失败则修改 prev 的 waitStatus 为 SIGNAL 后阻塞

当 head 释放锁,唤醒下一个节点,让其执行步骤3

公平锁使用 FairSync 实现,和非公平的区别有:

步骤1 不尝试加锁,所以并发低的场景下效率不如非公平实现

步骤2 会先检查CLH队列,如果前面有节点就不加锁,直接入队 CLH

2.3 读写锁实现

写锁,使其它线程将无法获取互斥锁、共享锁,状态记录在 state 低16位。实现参考 #2.2。

读锁,使所有线程都无法获取互斥锁,状态记录在 state 高16位。使用 AQS 为共享节点设计的方法,比如 acquireShared 等。

其中 setHeadAndPropagate 检查 next Node,如果共享(根据 nextWaiter 区分),则唤醒它。

这个被唤醒的 Node 也会唤醒 next(如果共享),从而形成一个传播唤醒。

2.4 Condition 实现

AQS 内部类 ConditionObject 维护条件队列,实现线程间的同步。

调用 await(),新增一个节点入队条件队列

当前线程释放全部锁,并阻塞

调用 signal(),从条件队列 head 寻找一个 Node 入队 CLH(只入队不唤醒)

2.5 分布式实现

3 CountDownLatch

闭锁,允许N个线程一直等待,直到其他线程执行的操作全部完成。

CountDownLatch(int count) // count 表示计数器

3.1 使用场景

数据库脱敏,每批次查询 1000 条,提交给线程池,线程在脱敏完成后 countDown,然后继续。

主线程 await 阻塞,等待脱敏完 1000 条记录后返回,并批量更新数据库。

3.2 AQS 实现

构造 CountDownLatch,设置 state 为 count

调用 await(),当 state > 0 入队 CLH,并阻塞

调用 countDown() 原子递减 state,当 state == 0 时遍历 CLH 并唤醒

3.3 Redis 实现

setCount() EXISTS key,如果不存在则 SET key count

countDown() DECR key,如果返回值 <= 0,则 DEL key

await() 在 while 循环中 GET key,如果 value <= 0 则结束循环

4 Semaphore

信号量,控制资源可被同时访问的线程个数。

Semaphore(int permits, boolean fair) // permits 许可个数;fair 公平竞争,默认 false

4.1 使用场景

限流。

4.2 AQS 实现

构造 Semaphore,设置 AQS state 为 permits

调用 acquire(N),剩余值 = state - N,如果小于0 则入队 CLH,否则 CAS 设置 permits 为剩余值

调用 release(N),CAS 设置 permis 为 state + N,遍历 CLH 并唤醒

4.3 Redis 实现

setPermits() GET key,如果 value == 0,则 SET key permits

acquire() GET key,如果 value >= premits,DECRBY permits。如果 value < permis,客户端自旋尝试获取。

release() INCRBY permits

5 CyclicBarrier

栅栏,让一组线程阻塞在一个同步点,满足数量后同时唤醒。CylicBarrier 基于 RentrantLock 实现,可以循环利用。

CyclicBarrier(int parties, Runnable barrierAction) // parties 预期到达栅栏的线程数;barrierAction 所有线程到达栅栏后执行的回调

5.1 使用场景

数据库脱敏,每批次查询 1000 条,提交给线程池,线程在脱敏完成后 await。

当 1000 个线程 await 时,CylicBarrier 唤醒其中一个线程执行批量更新。

5.2 ReentrantLock 实现

调用 await(),使用 ReentrantLock 加锁

检查所有到达栅栏的线程,如果中断则清空栅栏

检查到达栅栏的线程数,如果等于 parties 则唤醒所有线程,并重置栅栏

如果少于 parties 则使用条件阻塞

参考文献

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值