一:锁的原理结构
(1)锁对象内部维护了一个同步管理器的对象AbstractQueuedSynchronizer,AbstractOwnableSynchronizer
(2)该对象其实是一个抽象类,具体的锁的管理器继承该抽象类
(3)该抽象类的关键属性有:---->Thread exclusiveOwnerThread(获取锁的线程对象)
----> Node head(首节点,正在拥有当前锁的线程构造的Node对象)
---->Node tail(尾巴节点,等待获取锁的线程构造的双向链表结构的队列的最后一个节点)
---->int state(当前该锁被线程争抢的状态,如果state=0,表示无线程争抢该锁,如果state>0则表示已经有线程拥有该锁)
二:锁中的Node队列的结构
(1)所有的线程在执行到获取锁的代码的部分,都会调用同步管理器的lock()方法,如果有线程获取锁,则将该线程构造成node对象,添加到队列尾部,并调用系统命令阻塞当前线程。也就是代码执行到这,当前线程就不再执行。
(2)拥有锁的线程,在释放锁的时候,会唤醒自己的后继节点的线程,让其争抢锁。
(3)Node的内部结构属性
--->int waitStatus(当前node的线程在争抢锁过程的状态标识)
--->Node prev(当前node的上一个node的引用,前驱节点)
--->Node next(当前node的下一个INITALnode的引用,后继节点)
--->Thread thread(当前node所代表的线程的线程对象)
--->Node nextWaiter(下一个等待着的node)
(4)node等待状态的的含义
CANCELLED = 1:由于在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消等待,节点进入该状态不会再变化
SIGNAL = -1:后继节点线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点得以运行。
CONDITION = -2:在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()方法后,该节点将会从等待队列中转移到同步队列中。加入到对同步状态的获取中。
PROPAGATE = -3:表示下一次共享式同步状态获取将会无条件被传播下去。
INITIAL=0:初始化状态
三:公平锁和非公平锁的区别
(1)公平锁的锁获取,严格按照等待顺序进行锁获取,在获取锁的时候有一个判断hasQueuedPredeccssors(),同步队列中是否有前驱节点在等待获取锁,如果有,则放弃获取锁,而是添加到队尾,排队获取锁
(2)非公平锁,是获取锁的顺序是随机的,甚至,有的线程可能会一直无法获取锁,出现线程饥饿情况。
(3)公平锁的性能往往没有非公平锁的性能高,因为它需要排队,则需要进行程序状态的切换,要比非公平锁的切换次数多。
(4)公平锁:在获取锁的时候,先检查是否已经有队列形成。如果有,则加入队列。按顺序排队,获取锁。
(5)非公平锁:在获取锁的时候,会多次尝试获取锁,而不着急加入队列排队,更有主动权获取锁。如果多次获取也没获取成功,也加入排队,此时和公平锁一样。其两者的区别在于:非公平锁,加大了新晋线程抢到锁的概率。
四:锁的获取和释放的过程图
【LockSupport】工具的api
park():阻塞当前线程,如果调用unpark(Thread thread)方法或者当前线程被中断,才能从park()方法返回。
parkNanos(long nanos):阻塞当前线程,最长不超过nanos纳秒,返回条件在park()的基础上增加了超时返回。
parkUntil(long deadline):阻塞当前线程,直到deadline时间(从1970年开始到deadline时间的毫秒数)
unpark(Thread thread):唤醒处于阻塞状态的线程thread
park(Object blocker):阻塞当前线程,如果调用unpark(Thread thread)方法或者当前线程被中断,才能从park(Object blocker)方法返回。blocker用于问题排查和系统监控。
parkNanos(Object blocker,long nanos):阻塞当前线程,最长不超过nanos纳秒,返回条件在park(Object blocker)的基础上增加了超时返回。blocker用于问题排查和系统监控。
parkUntil(Object blocker,long deadline):阻塞当前线程,直到deadline时间(从1970年开始到deadline时间的毫秒数),blocker用于问题排查和系统监控。
======================公平锁和非公平锁=================
转载:全文地址请点击:https://blog.csdn.net/qyp199312/article/details/70598480?utm_source=copy
公平锁:
- 公平和非公平锁的队列都基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。公平锁则在于每次都是依次从队首取值。
- 锁的实现方式是基于如下几点:
- 表结点
Node
和状态state
的volatile
关键字。 sum.misc.Unsafe.compareAndSet
的原子操作(见附录)。
- 表结点
非公平锁:
- 在等待锁的过程中, 如果有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的。
ReentrantLock
锁都不会使得线程中断,除非开发者自己设置了中断位。
ReentrantLock
获取锁里面有看似自旋的代码,但是它不是自旋锁。
ReentrantLock
公平与非公平锁都是属于排它锁。
公平锁和非公平锁
ReentrantLock
的公平锁和非公平锁都委托了 AbstractQueuedSynchronizer#acquire
去请求获取。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire
是一个抽象方法,是公平与非公平的实现原理所在。addWaiter
是将当前线程结点加入等待队列之中。公平锁在锁释放后会严格按照等到队列去取后续值,而非公平锁在对于新晋线程有很大优势。acquireQueued
在多次循环中尝试获取到锁或者将当前线程阻塞。selfInterrupt
如果线程在阻塞期间发生了中断,调用Thread.currentThread().interrupt()
中断当前线程。
ReentrantLock
对线程的阻塞是基于LockSupport.park(this);
(见AbstractQueuedSynchronizer#parkAndCheckInterrupt
)。 先决条件是当前节点有限次尝试获取锁失败。
公平锁和非公平锁在说的获取上都使用到了 volatile
关键字修饰的state
字段, 这是保证多线程环境下锁的获取与否的核心。
但是当并发情况下多个线程都读取到 state == 0
时,则必须用到CAS技术,一门CPU的原子锁技术,可通过CPU对共享变量加锁的形式,实现数据变更的原子操作。
volatile 和 CAS的结合是并发抢占的关键。
CAS和volatile, Java并发的基石
volatile 是Java语言的关键字, 功能是保证被修饰的元素(共享变量):
任何进程在读取的时候,都会清空本进程里面持有的共享变量的值,强制从主存里面获取;
任何进程在写入完毕的时候,都会强制将共享变量的值写会主存。
volatile 会干预指令重排。
volatile 实现了JMM规范的 happen-before 原则。
在多核多线程CPU环境下, CPU为了提升指令执行速度,在保证程序语义正确的前提下,允许编译器对指令进行重排序。也就是说这种指令重排序对于上层代码是感知不到的,我们称之为 processor ordering.
JMM 允许编译器在指令重排上自由发挥,除非程序员通过 volatile等 显式干预这种重排机制,建立起同步机制,保证多线程代码正确运行。见文章:Java并发:volatile内存可见性和指令重排。
当多个线程之间有互相的数据依赖的之后, 就必须显式的干预这个指令重排机制。
CAS是CPU提供的一门技术。在单核单线程处理器上,所有的指令允许都是顺序操作;但是在多核多线程处理器上,多线程访问同一个共享变量的时候,可能存在并发问题。
使用CAS技术可以锁定住元素的值。Intel开发文档, 第八章
编译器在将线程持有的值与被锁定的值进行比较,相同则更新为更新的值。
CAS同样遵循JMM规范的 happen-before 原则。
看JAVA CAS原理深度分析博客
公平锁和非公平锁在说的获取上都使用到了 volatile
关键字修饰的state
字段, 这是保证多线程环境下锁的获取与否的核心。
但是当并发情况下多个线程都读取到 state == 0
时,则必须用到CAS技术,一门CPU的原子锁技术,可通过CPU对共享变量加锁的形式,实现数据变更的原子操作。
volatile 和 CAS的结合是并发抢占的关键。
--------------------- 本文来自 平菇虾饺 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/qyp199312/article/details/70598480