先从 Lock 讲起:
Lock
同步锁Lock是解决线程安全问题的常用手段,在并发包 java.util.concurrent 中。
Lock 是一个接口,具体方法如下:
void lock(); //线程获取锁,如果锁不可用,则阻塞休眠,直到获得锁被唤醒
void lockInterruptibly(); //与lock()方法类似,但阻塞的线程可中断,并抛出异常
boolean tryLock(); // 非阻塞线程尝试获取锁,获取成功返回true
boolean tryLock(long time, TimeUnit unit); //带有超时时间的获取锁方法
void unlock(); //释放锁
Lock的实现
- ReentranLock:可重入锁,是唯一一个直接实现了Lock接口的类。可重入的意思是,线程在释放锁后,可不需阻塞直接再次获得锁。不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。设计了加锁次数,以在解锁的过程中,保证所有的过程都解锁。
- ReentrantReadWriteLock:可重入读写锁,实现了 ReadWriteLock 接口。内部维护了 ReadLock和WriteLock两个锁,都实现了Lock接口,读写锁是一种适合读多写少的场景下解决线程安全问题的工具,基本原则:读和读不互斥,读和写互斥,写和写不互斥。即影响到数据变化的操作都会存在互斥。
AQS源码分析
AQS全称 AbstractQueuedSynchronizer,是一个抽象类,又称同步序列器。内部提供了一个FIFO队列,AQS可以看作 用来实现同步锁 以及 一系列依赖FIFO等待队列的同步功能的核心组件。
AQS为并发包中的同步工具类提供了通用的机制。
AQS的内部实现原理:AQS的实现依赖内部的同步队列,即FIFO双向队列(先进先出),这种结构的特点是每个节点都有两个指针,分别指向直接的前驱节点和直接的后置节点。
AQS使用一个int类型的成员变量state来表示线程的同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作。
因为ReentrantLock允许重入,所以同一个线程多次获得同步锁的时候,state会递增,比如重入5次,那么state=5。 而在释放锁的时候,同样需要释放5次直到state=0其他线程才有资格获得锁
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
内部类Node类组成:
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev; //前驱节点
volatile Node next; //后继节点
volatile Thread thread;//当前线程
Node nextWaiter; //存储在condition队列中的后继节点
//是否为共享锁
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
//将线程构造成一个Node,添加到等待队列
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
//这个方法会在Condition队列使用
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
锁竞争添加节点:
释放锁移除节点:
AQS中还有很多方法,下面结合ReentranLock进行讲解:
ReentranLock源码分析:
public void lock() {
sync.lock();
}
这是获取锁的入口,里面通过调用sync的 lock()方法 来获取锁。
abstract static class Sync extends AbstractQueuedSynchronizer
Sync类是ReentranLock里的静态内部类,继承了AQS这个抽象类,前面说过AQS是一个同步工具,主要用来实现同步控制。我们在利用这个工具的时候,会继承它来实现同步控制功能。;
Sync这个类有两个具体的实现,分别是 NofairSync(非公平锁)
,FailSync(公平锁):
- 公平锁 表示所有线程严格按照FIFO来获取锁
- 非公平锁 表示可以存在抢占锁的功能,也就是说不管当前队列上是否存在其他线程等待,新线程都有机会抢占锁
ReentranLock 默认为非公平锁,下面以非公平锁作主要分析。
NonfairSync.lock:
final void lock() {
// 非公平锁,通过CAS去抢占锁
if (compareAndSetState(0, 1))
// 抢占成功,保存获得锁的当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 走锁竞争逻辑,尝试去获取锁
}
acquire 是继承自AQS的方法,如果CAS操作未成功,表示当前state的值为0,acquire(1) 表示 锁竞争,想再次把线程状态置为1.
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
通过tryAcquire尝试获取独占锁,如果tryAcquire失败,则会通过addWaiter方法将当前线程封装成Node添加到AQS队列尾部。
acquireQueued,将Node作为参数,通过自旋去尝试获取锁。
tryAcquire 是重写AQS中的tryAcquire方法,作用是尝试获取锁,成功则返回true。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}