JUC包
JUC包目录。

其中包含了两个子包:atomic以及lock,另外在concurrent下的阻塞队列以及executors,这些类主要是依靠volatile以及CAS实现的。
整体结构如图:

Lock简介
Lock是一个接口。
与synchronized相比,Lock拥有了锁获取和释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
Lock是一个接口,有许多实现他的类,比如ReentranrLock,但是查看他的源码会发现大部分方法都是在调用他的内部类Sync的方法,而Sync继承了AQS,因此,Lock实现的核心还是AQS。

AQS
AQS的设计是使用模板方法设计模式,他将一些相同部分的代码实现,将不同的代码放到不同的子类中去;而且,在AQS的方法中,也会调用子类的代码。模板设计模式
例如:
1 | //子类需要重写tryAcquire() |
因此,AQS只需要实现各自不同的tryAquire()就行了,比如是公平锁还是非公平锁,是独占锁还是共享锁。
AQS提供的模板方法可以分为3类:
独占式获取与释放同步状态;
共享式获取与释放同步状态;
查询同步队列中等待线程情况;
AQS的功能分为两种:独占和共享
- 独占锁,每次只能有一个线程持有锁,比如ReentrantLock就是以独占方式实现的互斥锁.
- 共享锁,允许多个线程同时获取锁,并发访问共享资源,比如ReentrantReadWriteLock.
AQS实现
AQS中有两个重要的成员,一个是CLH队列,一个是state。
state
AQS使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。
CLH
CLH是一个先进先出的队列。如果当前线程竞争锁失败,那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
CLH的头节点是空的,啥也不存的。
1 | if (t == null) { // Must initialize |
Node
Node代表的是一个正在阻塞等待的线程。
1 | //当前节点处于共享模式的标记 |
独占锁的获取
1 | //通过子类的tryAcquire()获取锁,不同的子类有不同的实现,要是获取失败,则执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),将该线程放入CLH |
入队操作:主要采取CAS+自旋的方式,一开始采用CAS快速入队,失败了之后再采用自旋操作。
1 | //入队操作,mode = Node.EXCLUSIVE,独占锁 |
在把node插入队列末尾后,它并不立即挂起该节点中线程,因为在插入它的过程中,前面的线程可能已经执行完成,所以它会先进行自旋操作acquireQueued(node, arg),尝试让该线程重新获取锁!当条件满足获取到了锁则可以从自旋过程中退出,否则继续。
1 | final boolean acquireQueued(final Node node, int arg) { |
判断节点是否应该被挂起,当前驱节点是SIGNAL的时候,直接挂起线程。
1 | private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { |
acquire()整个流程:
调用子类的tryAquire()尝试获取资源,成功,直接返回。失败,继续。
获取失败,将该线程生成一个Node节点通过addWaiter(Node.EXCLUSIVE), arg)添加到等待队列。
插入等待队列后,防止在这个阶段资源又有了。根据前置节点状态状态判断是否应该继续获取资源。如果前驱是头结点,继续尝试获取资源;获取成功,返回;否则,继续。
在每一次自旋获取资源过程中,失败后调用shouldParkAfterFailedAcquire(Node, Node)检测当前节点是否应该park()。
如果前置节点是SIGNAL状态,就挂起,返回true。
如果前置节点状态为CANCELLED,就一直往前找,直到找到最近的一个处于正常等待状态的节点,并排在它后面,返回false,acquireQueed()接着自旋尝试。
前置节点处于其他状态,利用CAS将前置节点状态置为SIGNAL。当前置节点刚释放资源,状态就不是SIGNAL了,导致失败,返回false。但凡返回false,就导致acquireQueed()接着自旋尝试。
若返回true,则调用parkAndCheckInterrupt()中断当前节点中的线程。若返回false,则接着自旋获取资源。
parkAndCheckInterrupt()挂起线程。
共享锁的获取
共享锁就是同时可以有多个线程访问。实现与独占锁差不多,唯一的不同就是需要判断是否还有剩余资源。
1 | private void doAcquireShared(int arg) { |
公平锁
非公平锁的吞吐量较高例如默认状态的ReentrantLock 有新线程来了先争夺一下锁,没成功再去排队。
公平锁是java关键字synchronized的重锁模式,谁来了都乖乖排队,后来的线程不能争夺锁,一定要入队列等待前一个线程来unpark自己,除非队列里没有其他线程。
中断锁
当线程等待的时候,如果被interrupt(),那么直接抛出中断异常。
1 | public final void acquireInterruptibly(int arg) |
超时锁
在获取锁的过程中,超过某一个时长,自动放弃获取。
1 | private boolean doAcquireNanos(int arg, long nanosTimeout) |
释放锁
首先调用子类的tryRelease()方法释放锁,然后唤醒后继节点,(只唤醒一个节点)在唤醒的过程中,需要判断后继节点是否满足情况,如果后继节点不为空且不是作废状态,则唤醒这个后继节点,否则从tail节点向前寻找合适的节点,如果找到,则唤醒。
ConditionObject
这是AQS的一个内部类,其维护了一个condition队列。主要有await()和signal()等方法。
await()
await():当前线程处于阻塞状态,直到调用signal()或中断才能被唤醒。
- 将当前线程封装成node且等待状态为CONDITION。
- 释放当前线程持有的所有资源,让下一个线程能获取资源。
- 加入到条件队列后,则阻塞当前线程,等待被唤醒。
- 如果是因signal被唤醒,则节点会从条件队列转移到等待队列;如果是因中断被唤醒,则记录中断状态。两种情况都会跳出循环。
- 若是因signal被唤醒,就自旋获取资源;否则处理中断异常。
condition队列与CLH最大的不同就是CLH是双向列表,condition队列是单向列表。
CLH是单向列表的原因是可能需要获取前置节点的一些属性,比如说查看前置节点是不是头节点之类的。
ConditionObject详解
ConditionObject用来实现锁的等待通知机制。ConditionObject内部维护了一个等待队列,与CLH不同的是这个队列是单向链表。
与Object wait/notify区别
Object的wait和notify/notify是与对象监视器配合完成线程间的等待/通知机制,而Condition与Lock配合完成等待通知机制,前者是java底层级别的,后者是语言级别的,具有更高的可控制性和扩展性。
Condition能够支持多个等待队列(new 多个Condition对象),而Object方式只能支持一个。
Condition能够支持超时时间的设置,而Object不支持。
await方法
- void await() throws InterruptedException:当前线程进入等待状态,如果其他线程调用condition的signal或者signalAll方法并且当前线程获取Lock从await方法返回,如果在等待状态中被中断会抛出被中断异常;
- long awaitNanos(long nanosTimeout):当前线程进入等待状态直到被通知,中断或者超时。
- boolean awaitUntil(Date deadline) throws InterruptedException:当前线程进入等待状态直到被通知,中断或者到了某个时间。
signal方法
- void signal():唤醒一个等待在condition上的线程(第一个线程,条件队列是一个FIFO的队列),将该线程从等待队列中转移到同步队列中,如果在同步队列中能够竞争到Lock则可以从等待方法中返回。
- void signalAll():与1的区别在于能够唤醒所有等待在condition上的线程。
await实现原理
await主要做了三件事:
- 将线程包装成Node,插入到条件队列。
- 释放线程拥有的锁。
- 阻塞当前线程。
1 | public final void await() throws InterruptedException { |
插入到条件队列:
1 | private Node addConditionWaiter() { |
与等待队列不同的是,条件队列没有头节点。
释放锁:
1 | final int fullyRelease(Node node) { |
结束await()状态:
1 | //当前节点被移动到了同步队列中(即另外线程调用的condition的signal或者signalAll方法),while中逻辑判断为false后结束while循环 |