最近复习Java的多线程,突然想看看Java的锁是如何实现的,于是就折腾了一番,最后来到了一个关键的类AbstractQueuedSynchronizer,很久没读过源代码的我发现这个类真的很晦涩,哎老啦!
以下为正题:
要说Java的锁机制,还是要先介绍一下sun.misc.Unsafe这个类,这个可以说是底层JVM的一个很重要的API,里面大部分的方法都是native的,其它的方法就不介绍了,这里跟锁有关的主要为二种方法:
1. CAS方法(有必要说一下Compare And Set):
主要有三: compareAndSwapObject, compareAndSwapInt, compareAndSwapLong
与其配套的方法是: objectFieldOffset, staticFieldOffset
2. 线程控制方法:
二个:park, unpark
CAS方法主要用于原子操作,java几个原子类都是这些方法实现,如AtomicBoolean。一般的使用是:通过unsafe.objectFieldOffset(Field)获取属性在类对象里的偏移,然后使用unsafe.compareAndSwapInt(对象, 偏移, 期望值, 修改值)进行原子操作。
线程控制方法park/unpark就是就类似Thread的suspend/resume方法,park是暂停当前线程, unpark是恢复给定的线程, 那为什么不用Thread本来的方法呢,原因很简单suspend/resume在占用锁资源和死锁问题上臭名昭昭了,那park/unpark就不占用资源锁就不死锁了吗,是的如果先锁了资源再park那它跟suspend的效果一样,那问题就来了,既然一样,为什么?为什么?其间深意本人难以猜透,只能表达一下我的看法了吧:首先Unsafe不是JDK公开的API,正常情况下你都无法获取它(当然你可采用异常手段/反射的方式获取到它),它不像suspend/resume让你随意使用,这就避免了我们这些码农渣渣们不适当的使用;其次这两个方法就是为了构建Java的锁机制而产生的,它的使用规则就是在未获取锁之前park等待,unpark唤醒后继续参与锁的竞争,如果竞争不到又park,但一旦竞争到了之后就绝对不再park。
在线程控制的这两个方法上,我深切的感觉到,Java的设计者们想把一切都暴露给我们,而又怕我们这些渣渣毁了Java的名声,所以只把这些方法给牛人使用,让他们做成通用的基础组件instruments再来给我们这些渣渣用。这也说明了东西本没有好坏,要看用它的人。
XX的,花了这么久讲这一个这么简单的类,真啰嗦,加速
Java用java.util.concurrent.locks.LockSupport对Unsafe进行了初级封装,支持park(泊车)时间,作为基础组件。
而后就来到了很关键的一个类:java.util.concurrent.locks.AbstractQueuedSynchronizer
这个类关是doc就长的要命,我是边翻词典边看,终于看完了它的类doc,终于明白它就是锁相关的关键类了。这里面涉及一个名词:CLH,就是三个多么多么牛逼人物名的首字母啦,那首先想到的就是什么关键的算法和数据结构了。对了,java就是靠这个CLH,加前边Unsafe的两类方法实现了基本的锁机制。
其实这个CLH结构也很简单,难懂的是它的使用规则,Java使用的结构就是一个双向链表,如下:
head --> 结点 <--> 结点 <--> ... <--tail
每个结点主要有四个字段: waitStatus(等待状态), prev(前一个结点), next(后一个结点), thread(关联的thread), 还有一个与基本机制关系不大(暂没研究,用于共享锁实现)的属性nextWait.
这里面很重要的一个就是waitStatus了,waitStatus有值:CANCELLED, 0, SIGNAL, CONDITION。
好了,后面我真不知道该如何说下去了,因为我也没全部看懂,就试着表达一下吧。
首先这个锁的大体思想是这样的:
1. 一个线程要来获取一个锁,为了提高效率先尝试直接获取锁,如果成功,高高兴兴直接走人了。如果不成功,玩了,先领一个结点进队列排队吧。
2. 线程排完队后并没有停止,他就像一个地道的天朝人,有各种想法:万一前面的人完事了,没叫我呢,万一前面的人不想排了,走了呢。于是他不安心了:
1> 首先他要看前面有几个人,如果前面就一个人了(prev==head),那他很兴奋,就会急不可耐的问一下:哥们完事了吗(trayAquireLock)?万一那哥们完事了正在整理东西呢,是不!如果得到的回答是肯定的,他开心的一下把冲前面把人挤走了(setHead(自己)),获取锁成功。无论是前面不止一个人,还是一个人时如果得到的回答是否定的(获取锁失败),不要紧,一样兴奋。
2> 首先他问前面的人,你当前想不想排队啊,你是不是打算放弃了啊;如果前面的人打算放弃了,他又去问更前面的一个人,直到问到一个当前不打算放弃的人为止,并且立马插对到这个人之后。好了,安心了吗,没有
3> 现在他到了一个不打算放弃(cancel)的人后面了,他又要考虑了,他对前面的人说:如果你好了一定要记得通知我哦~~。他并不是说完这个话就去睡觉了,他还是不放心啊,他一定要得到前面的人的明确答应(使用CAS更新前一个Node的waitStatus为signal成功),如果没有得到明确答应(CAS操作不成功),不要紧,主角经得住各种打击,他又从1>再走一遍流程。直到获取锁成功或者前者答应他的请求。
4> 如果他获锁成功当然高高兴兴走了,如果仅仅是前者答应他的请求了,那他就要开始慢长的等待了。他早了一个好沙发(Unsafe)睡觉去了(park)。
3 如果前面有锁的人办完事了,就会释放锁(release),此时这个办完事的人就会想了,是不是有人要我提醒他的(看自己的waitStatus是不是signal),如果是,好,提醒后面的人,如果不是,好,结束走人。
1> 这里提醒后面的人也不是那么简单的,他首先看后面这个人是不是想放弃了,如果后面这个人已经等得心灰意冷,好吧,为了防止心灰意冷的人忘了通知他面的人(办完事就是开心,很热心的)他会跑到队尾一个一个往前面问,把所有人都问一遍,找到第一个不想走的人,唤醒之,然后走人了。
4 这里要特别说明的是如果在获取锁过程中,或在排队过程中,有某个要不想玩了(cancel),他需要做一件事,首先标识自己是cancelled,然后往前找,直到找到还想玩的人a,然后看自己身后的人,如果自己是做后一个,好办,直接告诉a你是取后一个了(使用CAS操作), 如果再告诉的瞬间来了一个排队,也好办,如果
a不是第一个,就要求a办完通知后面的(CAS waitStatus),如果a本来就是这个状态,或通知成功,那直接把后面的小弟交给a吧,如果a是第一个,或者通知失败,好吧,叫醒小弟,让小弟像第2步一样,自己求人去。
好了,说的我都不懂了,打住吧,这里面有些还是没想通,就是同时有多个线程做cancel操作,每人都在做上面第4步的操作,不知道会不会出现什么意想不到的情况。
费时啊,先结束吧!
相关推荐
Java同步机制是多线程编程中确保数据一致性与正确性的关键。在Java中,主要有两种同步机制:内置的`synchronized`关键字以及基于`java.util.concurrent`包中的高级同步工具类。本文将深入探讨这些机制的底层实现,...
Java并发编程之同步器代码示例 ...Java并发编程之同步器代码示例展示了Java中的同步器机制,包括CountDownLatch、Semaphore、Barrier和Exchanger队列同步器等,帮助开发者更好地理解和使用Java中的并发编程技术。
基于JDK层面的锁主要可以分为四种方式,其中AbstractQueuedSynchronizer(AQS)是Java SDK并发包中主要的同步器实现。 AQS是Java中解决同步和互斥问题的基础同步器,通过Lock和Condition两个接口来实现管程。Lock...
本文将基于JDK源码解析Java领域中的并发锁,探讨AQS基础同步器、LockSupport、Condition接口、Lock接口、ReadWriteLock接口以及自定义API操作的设计与实现。 一、AQS(AbstractQueuedSynchronizer)基础同步器的...
开发者可以通过继承AQS来实现自己的同步器,例如实现一个互斥锁、读写锁、信号量等。 AQS的实现原理是基于队列的数据结构,每个线程都可以被看作是一个节点,节点之间可以形成队列。AQS通过对队列的操作来实现同步...
AQS 是队列同步器 AbstractQueuedSynchronizer 的简写,用来构建锁或者其他同步的基础。AQS 使用一个 int 成员变量来表示同步状态,通过内置 FIFO 队列完成竞争资源的线程排队工作。AQS 提供方法主要有三类:独占式...
`java.util.concurrent`包中的`AbstractQueuedSynchronizer`框架为Java开发者提供了一个强大的工具箱,使得他们能够高效地实现各种同步器。通过对同步状态的有效管理、阻塞和唤醒机制的优化以及灵活的扩展性设计,...
同步锁是Java 1.0版本就引入的机制,主要通过`synchronized`关键字实现。它确保对同一对象的同步访问,即在同一时刻,只有一个线程能够执行特定的代码块或方法。同步锁的特性包括: 1. **互斥性**:当一个线程持有...
文档中提到了AbstractQueuedSynchronizer类,这个类是Java 5.0中java.util.concurrent包大多数同步器的基础,如锁、屏障等同步器。这些同步器以AbstractQueuedSynchronizer为基础框架,提供了原子管理同步状态、阻塞...
它是实现Java并发包中锁和其他同步器的基础框架,例如ReentrantLock(可重入锁)、Semaphore(信号量)、CountDownLatch(倒计时门闩)、CyclicBarrier(循环栅栏)以及ReentrantReadWriteLock(可重入读写锁)等。...
AQS(AbstractQueuedSynchronizer)是Java.util.concurrent包中同步器的基础框架,它的核心设计思想与实现方法在Doug Lea先生的这篇论文中有详细的介绍。论文详细阐述了AQS框架的原理、设计、实现、应用以及性能等...
1. **AQS(AbstractQueuedSynchronizer)基础同步器**: AQS是一个用于构建锁和同步器的框架,它维护了一个FIFO的等待队列,提供了两种模式:独占和共享。ReentrantLock和Semaphore等都是基于AQS实现的。 2. **...
8. 锁和同步器 锁是实现线程同步的关键机制,Java提供了不同类型的锁,包括乐观锁、悲观锁、自旋锁等。自旋锁利用了CPU空转等待,减少了线程上下文切换的开销。公平锁和非公平锁的区别在于线程获取锁的顺序。读写锁...
6. **AQS(AbstractQueuedSynchronizer)**:AQS是Java并发库中的核心组件,它是一个抽象的队列同步器,提供了构建锁和同步器的基础框架。很多并发工具类如`ReentrantLock`、`Semaphore`、`CountDownLatch`等都是...
ReentrantLock的核心实现依赖于AQS,这是一个抽象的队列同步器。AQS维护了一个状态字段和一个FIFO等待队列,用于管理线程的同步。ReentrantLock的内部类Sync继承自AQS,进一步分为FairSync(公平锁)和NonfairSync...
AQS是Java并发包中的抽象队列同步器,它是ReentrantLock、Semaphore等并发工具的基础,通过维护一个FIFO等待队列来管理线程的等待和唤醒。 9. **CAS(Compare and Swap)**: CAS是一种无锁算法,用于更新变量。...
4. **同步器(Synchronizers)**:JUC库中的同步器主要是通过AQS(AbstractQueuedSynchronizer)来实现的。AQS是一个抽象类,为锁和同步器提供了一种通用的底层实现。它维护了一个等待队列,提供了基于FIFO的等待...
AbstractQueuedSynchronizer(AQS)是Java并发编程中的核心组件,它是一个抽象的、基于FIFO(先进先出)等待队列的同步器。许多并发工具类,如ReentrantLock、CountDownLatch、CyclicBarrier和Semaphore等,都基于...