锁机制
锁的基本原理

指令由cpu执行,任何语言程序的锁都是由指令cmpxchg,全称 compare and exchange:
cpu 要将count数值更改,他会先从内存中加载此值
cpu执行cmpxchg,将会:
比较 当前持有的值与内存中的值是否相同
如果相同代表数值没有被修改,将会替换值,并返回成功
如果不相同则代表数值有可能被修改,并返回失败
此外,CPU为了满足cmpxchg指令的原子性,为其增加了lock:
所有的锁都基于cmpxchg指令(x86),在Java中,有一个native方法映射了该指令:
如何实现一个锁
如何表示锁的状态
boolean state,只能表示两种状态int state不仅可以表示状态,还可以记录锁被持有的数量,这样可以提供对重入锁的支持
保证多线程抢锁线程安全
基于
lock cmpxchg,对应javaUnsafe.compareAndSwapInt方法,也就是CAS
处理没有获取到锁的线程
自旋等待:
缺点,后台线程自旋等待,会占用CPU,导致性能障碍
优点,适用于并发低,任务耗时短的操作,因为处理快,每个线程自旋一会儿就会获取到锁
注意,当并发数逐渐增大或者任务耗时较长时,也就是没有获取锁的等待线程数量增大,自旋的线程数量以及时间会增长,会导致cpu自旋的压力增大
阻塞等待
交给操作系统将其阻塞
当自旋锁消耗的时间大于阻塞上下文切换的时间时,该使用阻塞
自旋 + 阻塞
释放锁后如何将锁重新分配
自旋锁:每个线程在不停的循环,他们会自己抢锁
阻塞锁:唤醒线程
AQS
全称AbstractQueueSynchronizer:
Abstract表示该类并不知道该如何上锁,使用了模板设计模式,暴露钩子函数给子类
Queue表示线程阻塞队列,抢不到锁的线程都会处于这个队列中排队
Synchronizer翻译为同步器,因为他可以保证线程安全
AQS底层实际上是使用 CAS + state完成多线程抢锁的逻辑
总结:子类只需要实现上锁、释放锁的逻辑,至于排队阻塞等待、唤醒机制均由AQS实现
核心代码:
上锁:
释放锁:
ReentrantLock
ReentrantLock 是基于AQS实现的可重入锁的实现类,他有公平锁、非公平锁两种实现:
公平锁
线程D进入后,检查阻塞队列,如果阻塞队列有阻塞线程,线程D就在后面排队

非公平锁
线程D进入后,直接抢锁

所以公平锁的效率是不如非公平锁的,这是因为公平锁需要线程上下文切换时间以及调度的延迟时间,而非公平锁在线程D进入时,先抢锁,不存在上述两个时间。
释放锁
ReentrantReadWriteLock
基于ReentrantLock的读写锁,拆分为两把锁,读锁和写锁。适用于读多写少的场景。读读不互斥,读写互斥,写写互斥。
核心变量与构造器
读写锁的抽象接口,用于返回一对读写锁:
最后更新于
这有帮助吗?