Lock体系是JDK1.5增加的,它访问共享资源的访问机制与内建锁完全不同!
它没有内建锁隐式的加锁与释放锁,它增加了可中断获取锁及超时响应机制获取锁的特性,这些特性是内建锁完全不具备的!
AQS:是Lock体系核心的存在,是构建锁与其他同步器件的基础框架,是由一个int整型表示其同步状态及FIFO队列组成,一般使用AQS都会创建一个静态内部类继承AQS。
Lock–>面向使用者,定义了使用者与锁的接口,隐藏了实现细节
AQS–>面向实现者,对使用者屏蔽了同步状态的管理,线程的排队,等待,唤醒等机制;
Lock体系可以有多个等待队列,而内建锁只有一个等待队列;
等待队列:调用wait()方法被阻塞的线程
同步队列:获取锁失败的线程进入同步队列,Lock体系的同步队列是由一个带有头尾指针的双向链表组成,是把线程封装成一个Node节点放在链表中;
Lock接口的API
1.void lock();
2.boolean tryLock();//获取锁成功返回true,否则返回false
3.boolean tryLock(long,TimeUnit)// 超时等待获取锁,在二的基础上进行重载
4.void lockInterruptibly() throws InterruptedException;获取锁的过程中可以响应中断
5.void unLock()//解锁
Lock接口的常用实现子类
1.ReentrantLock //可重入锁
2.ReentrantReadWriteLock //可重入读写锁
AQS中的protected方法-需要被子类覆写
1.tryAcquire()--独占式获取锁,成功返回true
2.tryRelease()--释放锁 成功返回true
3.isHeldExclusively()--当前线程是否持有锁
一般Lock接口的实现子类里都会有一个静态内部类继承AQS,覆写AQS中的提供给子类覆写protected方法,然后实现,所以最后在调用AQS中的方法时其实就会调用子类实现的方法!(这里用到了模板设计方法)
ReentrantLock里的lock方法,很显然直接调用了sync.lock()!
1.
public void lock() {
sync.lock();
}
sync.lock()-----》
final void lock() {
//先通过CAS尝试将锁的状态设置为自己的线程状态
if (compareAndSetState(0, 1))
//设置成功的话将当前同步状态改为自己的线程
setExclusiveOwnerThread(Thread.currentThread());
else
//不成功调用acquire()方法
acquire(1);
}
acquire(int arg)方法–>独占式 获取锁的模板方法
调用AQS的acquire(int arg)
public final void acquire(int arg) {
//尝试获取锁,如果成功则直接返回,否则继续向下执行
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter(Node.EXCLUSIVE),从方法的名字可以大致看出来,加入同步队列,也就是刚才获取同步状态失败的锁加入进去
private Node addWaiter(Node mode) {
///将当前线程尾插入同步队列
//mode是模式的意思,在Lock体系中有两大模式,独占式和共享式,这里需要知道传进来的参数独占式还是共享式
//将当前线程以指定的模式封装成Node节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//拿到当前同步队列的尾节点
Node pred = tail;
//如果当前尾节点为不为空,则进行尾插法
if (pred != null) {
node.prev = pred;
//将当前节点尾插入同步队列
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//否则,调用enq(node)方法
enq(node);
return node;
}
若当前同步队列为null或者尾插失败会调用enq()
当同步队列为null时初始化链表之后,使用CAS将节点尾插入链表
private Node enq(final Node node) {
//死循环,在这里也就是自旋
for (;;) {
Node t = tail;
if (t == null) {
//初始化当前链表,采用 lazy-load初始化,有头节点的双链表 头节点不存放数据
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
//再次采用CAS将节点尾插入链表中
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
将节点尾插入同步队列之后,调用accquireQueued(Node node, int arg)方法
节点在同步队列中排队获取同步状态
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
//设置中断状态
boolean interrupted = false;
//自旋
for ( ; ;) {
//获取当前节点的前驱节点
final Node p = node.predecessor();
//如果前驱节点为头节点并且尝试获取锁成功的话
if (p == head && tryAcquire(arg)) {
//将当前节点设置为头节点
setHead(node);
//将前驱节点出队
p.next = null;
failed = false;
return interrupted;
}
//此方法看下面
if (shouldParkAfterFailedAcquire(p, node) &&
//阻塞当前节点
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
if (p == head && tryAcquire(arg)) 在同步队列中,当前节点获取锁的前提–>只有当前驱节点为头节点线程才有机会获取同步状态
当前线程获取同步状态失败时,首先调用shouldParkAfterFailedAcquire(p,node)
方法作用:一直在自旋,尝试将前驱节点改为Node.SIGNAL,表示当前节点应该被阻塞!
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前驱节点的状态
int ws = pred.waitStatus;
//如果前驱节点为SIGNAL,则表示当前节点应当被阻塞
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
//如果前驱节点状态值大于0,即表示为取消状态
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
//循环中一直找到当前节点的前驱节点不为SIGNAL状态
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//通过CAS方法将前驱节点状态设置为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
在这里插入图片描述
独占式锁的释放