1. 简介
在前面的文章中我们介绍了lock接口,AQS同步器,重入锁,condition接口,这篇文章将准备将以上知识点在从源码上梳理一遍。
2. Lock
在java5以后,增加了JUC的并发包且提供了Lock接口用来实现锁的功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
Lock是一个接口,核心的两个方法lock和unlock,下面是Lock接口源码:
public interface Lock {
//获取锁
void lock();
//获取锁的过程能够响应中断
void lockInterruptibly() throws InterruptedException;
//非阻塞式响应中断能立即返回,获取锁放回true反之返回fasle
boolean tryLock();
//超时获取锁,在超时内或者未中断的情况下能够获取锁
boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;
//释放锁
void unlock();
//获取与lock绑定的等待通知组件,当前线程必须获得了锁才能进行等待,进行等待时会先释放锁,当再次获取锁时才能从等待中返回
Condition newCondition();
}
3. AbstractQueuedSynchronizer(AQS抽象队列同步器)
AQS是用来构建锁或者其他同步组件的基础框架,通过一个int变量来表示当前同步状态,AQS依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失时,AQS会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
Node属性如下:
static final class Node {
int waitStatus; //表示节点的5种状态
Node prev; //前继节点
Node next; //后继节点
Node nextWaiter; //存储在condition队列中的后继节点
Thread thread; //当前线程
}
其中节点状态:
- Cancelled,值为1,由于在同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待,节点进入该状态将不会变化
- Signal,值为-1,后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行
- Condition,值为-2,节点在等待队列中,节点线程等待在Condition 上,当其他线程对Condition 调用了 signal()方法后,该节点将会从等待队列中转移到同步队列中,加入到对同步状态的获取中
- Propagate, 值为-3, 表示下一次共享式同步状态获取将会无条件地被传播下去
- Initial, 值为0,初始状态
4. 从ReentrantLock重入锁开始
ReentrantLock重入锁是接口Lock 的一种实现,他支持重进入,除此之外,该锁的还支持获取锁时的公平和非公平性选择。下面是重入锁的部分源码:
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
//构造函数,默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//构造函数,可以选择是否公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//非公平锁
static final class NonfairSync extends ReentrantLock.Sync{
.....
}
//公平锁
static final class FairSync extends ReentrantLock.Sync{
......
}
//继承AQS(同步器)
abstract static class Sync extends AbstractQueuedSynchronizer {
.....
}
//线程等待队列
public Condition newCondition() {
return this.sync.newCondition();
}
下面开始一个简单例子:
private static int count = 0;
static Lock lock = new ReentrantLock();
public static void inc() {
lock.lock();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
lock.unlock();
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
RLDemo.inc();
}).start();
}
Thread.sleep(3000);
System.out.println("result:" + count);
}
在上面的例子中,生成1000个线程,每个线程都调用inc方法实现count+1。这里补充下,Lock 一般使用static修饰,作为类的一个属性。
在类加载的时候发现static修饰的lock 就会初始化new ReentrantLock()。重入锁的构造函数,就知道他去生成了一个非公平的重入锁实例,而重入锁是通过AQS生成的,AQS是继承AbstractOwnableSynchronizer抽象类。AbstractOwnableSynchronizer只是记录当前被哪个线程持有。ReentrantLock初始化时序图如下:
ReentrantLock初始化完成后会有三个属性:
- Node head ,头节点来源于AQS中Node
- Node tail 尾点来源于AQS中Node
- int state 同步状态
- Thread exclusiveOwnerThread 当前持有同步状态的线程来源于AQS父类中AbstractOwnableSynchronizer
如图:
lock初始完成后,将会调用其lock方法加锁,注意lock()、unlock方法是对于锁的使用者来说,而lock之后是对于锁的开发者。