目录
概述:
通过继承它来实现父类的功能
获取锁:
阻塞和获取资源用的是park和unpark;
释放锁:
判断是否有锁资源:
AbstractQueuedSynchronizer公有方法(AQS)
1.acquire:独占式获取同步状态;
2.release:独占式释放同步状态;
3.acquireShared(int arg):
共享式获取同步状态,如果当前线程未获取到同步状态,那么就进队列等待——>与独占式的主要区别是在同一时刻可以有多个线程获取同步状态;
4.releaseShared(int arg):共享式释放同步状态;
需要由子类实现的保护方法
1.tryAcquire:独占式获取同步状态;——通过cas操作设置锁资源占有状态,并且对锁的Owner进行设置
2.tryRelease:独占式释放同步状态;
3.getState:返回锁状态
还有几个shared
其他方法
锁和同步器的区别
锁:面向使用者,定义了锁和使用者交互方式 ,隐藏了细节;
同步器:面向的是锁的实现者,简化了锁的实现方式;
锁和同步器很好的隔离了使用者和实现着所需要关注的领域;
自定义锁-独占锁
package com.example.juc.AQS;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import static java.lang.Thread.sleep;
/**
* @author diao 2022/4/23
*/
@Slf4j(topic = "c.TestAqs")
public class TestAqs {
public static void main(String[] args) {
MyLock lock = new MyLock();
new Thread(()->{
lock.lock();
log.debug("locking...");
lock.lock();//synchronized和ReentrantLock都是可重入
try {
log.debug("locking...");
sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log.debug("unlocking...");
lock.unlock();
}
},"t1").start();
// new Thread(()->{
// lock.lock();
// try {
// log.debug("locking...");
// } finally {
// log.debug("unlocking...");
// lock.unlock();
// }
// },"t2").start();
}
}
//自定义锁(不可重入锁);可重入锁:拿到锁资源下次进入一样可以进入
class MyLock implements Lock{
//独占锁,AQS基于队列(同步器)
class MySync extends AbstractQueuedSynchronizer{
/*1.尝试获取锁资源*/
@Override
protected boolean tryAcquire(int arg) {
//因为可能会有多个线程竞争锁资源,所以要进行cas
if(compareAndSetState(0,1)){
//尝试将锁的owner设置为当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/*2.尝试释放锁资源*/
@Override
protected boolean tryRelease(int arg) {
//这里需要注意顺序,setState有volatile字段
// 有读写屏障,前面的修改锁拥有资源对其他线程都可见
setExclusiveOwnerThread(null);
setState(0);
return true;
}
/*3.判断是否持有独占锁*/
@Override
protected boolean isHeldExclusively() {
return getState()==1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
private MySync sync=new MySync();
@Override//加锁(不成功,会进入等待队列)
public void lock() {
sync.acquire(1);
}
@Override//加锁,可打断
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override//尝试加锁,带超时
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
@Override//解锁
public void unlock() {
/**
* 和sync中的tryRelease是不一样的
* 上面的只是改变锁的拥有者以及锁的state,但并不会唤醒被阻塞的线程
* release可唤醒阻塞线程
*/
sync.release(1);
}
@Override//返回条件变量
public Condition newCondition() {
return sync.newCondition();
}
}
如代码所示,独占锁实现了在同一时刻只能用一个线程获取到锁,而其他获取锁的线程只能在等待队列等待;
源码分析
然后我们看看release方法源码
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
然后看看unparkSuccessor方法 ,会发现实际上核心就是unpark方法
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
node.compareAndSetWaitStatus(ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
if (s != null)
LockSupport.unpark(s.thread);
}
像我们的ReentrantLock就是由AQS同步器实现的
比如说lock()方法——>实际上是调用了同步器的lock方法,然后调用里面的acquire()方法(阻塞和非阻塞)
1.ReentranLock中的lock()
public void lock() {
sync.acquire(1);
}
2.然后进入同步器中的acquire方法
2.1如果tryAcquire()加锁成功或者重入成功,后面的就不用执行了(将节点封装node传入阻塞队列)
2.2 如果没拿到锁为false,就会进入后面的操作
//以独占模式获取,忽略中断。通过调用至少一次 tryAcquire 来实现,成功返回。否则线程排队,可能///重复阻塞和解除阻塞,调用 tryAcquire 直到成功。此方法可用于实现方法 Lock.lock。参数:arg -// 获取参数。这个值被传递给 tryAcquire 但不会被解释并且可以代表你喜欢的任何东西
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
2.2阻塞队列,线程没拿到锁的情况(AQS提供的)
第一个节点总是空的,后面的才是带线程的,入队等待
private Node addWaiter(Node mode) {
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
node.setPrevRelaxed(oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
3.然后我们看看AQS同步器中的tryAcquire(),这个是需要Reentranlock实现增强的,就是cas操作设置0,1状态,然后将拥有这设置为当前线程返回true
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
3.1如果是不公平锁实现的
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
会进行状态state的判断,如果值不为0说明锁就被占用了,如果为0说明占有成功返回true,加锁成功后会记录当前线程是有锁线程(两次)——>state!=0说明锁已被占用,判断当前线程身上是否有锁,如果有则重入(0+1,体现出了两次判断——>没拿到锁可能自己身上有锁资源)
加锁成功或者重入成功都返回true
@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
4.然后我们看看下面的acquireQueued()方法,目的就是封装为一个node节点传入阻塞队列中
unlock解锁流程
1.本质上就AQS的release方法,以独占模式发布。如果 tryRelease 返回 true,则通过解锁一个或多个线程来实现。此方法可用于实现方法 Lock.unlock。参数:arg - 释放参数。这个值被传递给 tryRelease,但不会被解释并且可以代表任何你喜欢的东西。返回:从 tryRelease 返回的值
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
2.然后点进tryRelease()方法
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
然后看看它在ReentranLock中的实现
对state的值进行-1然后判断——>1.判断减后是否为0,为0解锁成功返回true,并且将当前拥有线程设置为null;2.如果不为0就为false
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
AQS小结
使用方式?
AQS主要是通过继承的方式,子类通过继承同步器并实现它的抽象方法->来管理同步状态;
如何管理同步状态?
AQS使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的。
特点:
AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作,如果当前线程获取同步状态失败时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程;
站在使用角度,我理解其实就两个功能:独占和共享,要么独占要么共享,然后调用底层api;
AQS的大致实现思路
AQS内部维护了一个CLH队列来管理锁。线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync queue里。 接着会不断的循环尝试获取锁(自旋),条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。