先看自己实现的一个AQS的代码示例
package mashibing;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class MyAQSDemo {
private MyLock myLock;
public MyLock getMyLock() {
return new MyLock();
}
class MyLock extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int acquires) {
Thread currentThread = Thread.currentThread();
/**
* state的含义
* 0表示没有线程持有锁
* >1表示有线程持有锁
*/
int state = getState();
if (state == 0) { // 如果当前线程没有锁 就拿着锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(currentThread);
return true;
}
} else if (currentThread == getExclusiveOwnerThread()) { // 如果当前线程已经持有该锁了 就重入
int newState = state + acquires;
System.out.println("此时的状态是" + newState + "线程" + Thread.currentThread().getName());
setState(newState);
return true;
}
return false;
}
@Override
protected 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;
}
public void showLockThread() {
if (getExclusiveOwnerThread() != null) {
System.out.println("当前线程:" + getExclusiveOwnerThread().getName() + "被锁住了");
}
}
public void lock() {
acquire(1);
}
public void unLock() {
release(1);
}
}
public static void main(String[] args) throws InterruptedException {
MyAQSDemo myAQSDemo = new MyAQSDemo();
MyLock myLock = myAQSDemo.getMyLock();
Thread thread1 = new Thread(() -> {
try {
System.out.println("线程1启动---------");
myLock.lock();
System.out.println("线程1被锁住了-------");
Thread.sleep(5000);
// myLock.lock();
// System.out.println("线程1再次被锁住了-------");
// Thread.sleep(5000);
} catch (InterruptedException e) {
} finally {
myLock.unLock();
// myLock.unLock(); //锁了两次的话 就要解锁两次 这就是可重入锁
}
}, "线程1");
Thread thread2 = new Thread(() -> {
try {
System.out.println("线程2启动---------");
myLock.lock();
System.out.println("线程2被锁住了-------");
Thread.sleep(5000);
} catch (InterruptedException e) {
} finally {
myLock.unLock();
}
}, "线程2");
thread1.start();
thread2.start();
Thread.sleep(2000);
myLock.showLockThread();
thread1.join();
thread2.join();
System.out.println("整个方法结束了");
}
}
AQS中定义了一个变量state
state == 0 的时候说明当前线程没有获取锁
state >0 的时候说明当前线程获取了锁(用int而不用bool类型,是因为允许共享模式中多个线程同时获取锁)
AQS提供了三个方法来对state进行操作 getState setState compareAndSetState(int expect ,int update)
当线程获取锁失败失败时候,他会生成一个节点,并将节点放到AQS内中的FIFO队列中等待获取锁。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire这个是使用者自己重写的方法,如果没有重写,抽象类本身会抛出异常。
如果tryAcquire(arg) == true 说明成功获取锁了,那么接下来就执行线程代码即可
如果tryAcquire(arg)==false 说明没有成功获取锁, 接下来就走addWaiter
private Node addWaiter(Node mode) {
// 新建node
Node node = new Node(Thread.currentThread(), mode);
// 快速的将当前尾节点设置为我要加的尾节点的前节点
Node pred = tail;
if (pred != null) {
node.prev = pred;
// CAS设置尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 多次尝试
enq(node);
return node;
}
private Node enq(final Node node) {
//多次尝试,直到成功为止
for (;;) {
Node t = tail;
//tail不存在,设置为首节点
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
//设置为尾节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
此时就将未获取到锁的线程放到了FIFO的尾节点。
接下来调用acquireQueued
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; // help GC
failed = false;
return interrupted;
}
// 获取锁失败的话就停住
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
无论如何都会返回interrupted,如果为true的话说明获取锁失败,那么就自己停住。否则就继续执行。
当线程获取同步状态后,执行完相应逻辑后就需要释放同步状态。AQS提供了release(int arg)方法释放同步状态:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
// 如果头节点不为空且头节点的等待状态不是0。那么就将头节点的停止状态取消掉
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return
private void unparkSuccessor(Node node) {
//当前节点状态
int ws = node.waitStatus;
//当前状态 < 0 则设置为 0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//当前节点的后继节点
Node s = node.next;
//后继节点为null或者其状态 > 0 (超时或者被中断了)
if (s == null || s.waitStatus > 0) {
s = null;
/*从tail节点来找可用节点
*为什么不直接从头开始搜索,而是要花大力气从后往前搜索?这个问题很好,其实是和addWaiter方法中,前后*两个节点建立连接的顺序有关。我们来看:
*1.后节点的pre指向前节点
*2.前节点的next才会指向后节点。
*这两部操作在多线程环境下并不是原子的,也就是说,如果唤醒是从前往后搜索,那么可能前节点还未建立好,那么搜索将可能会中断。*/
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//唤醒后继节点
if (s != null)
LockSupport.unpark(s.thread);
}
此时就会将最先锁住的线程释放掉 .
上面说的都是独占模式下的,接下来看看共享模式下有什么区别?
private void doAcquireShared(int arg) {
// 包装成共享模式的节点,入队
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
// 自旋
for (;;) {
final Node p = node.predecessor();
if (p == head) {
// 尝试获取同步状态,子类实现
int r = tryAcquireShared(arg);
if (r >= 0) {
// 设置新的首节点,并根据条件,唤醒下一个节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
主要区别在于:
1、在共享式获取资源失败的时候,会包装成SHARED模式的节点入队。
2、如果前驱节点为head,则使用tryAcquireShared方法尝试获取同步状态,这个方法由子类现。
3、如果获取成功r>=0,这时调用setHeadAndPropagate(node, r),该方法首先会设置新的首节点,将第一个节点出队,接着会不断唤醒下一个共享模式节点,实现同步状态被多个线程共享获取。
private void doReleaseShared() {
// 自旋
for (;;) {
Node h = head;
// 队列已经初始化且至少有一个节点
if (h != null && h != tail) {
int ws = h.waitStatus;
// 无论是独占还是共享,只有节点的ws为signal的时候,才会在释放的时候,唤醒后面的节点
if (ws == Node.SIGNAL) {
// cas将ws设置为0,设置失败,将会继续从循环开始
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒后继节点,unparkSuccessor这个方法上面已经解析过
unparkSuccessor(h);
}
// 如果ws为0,则更新状态为propagate,
// 之后setHeadAndPropagate读到ws<0的时候,会继续唤醒后面节点
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果head在这个过程中被更改了,会继续自旋
if (h == head) // loop if head changed
break;
}
}
大致理解就是共享模式下,放到队列的线程都是SHARED模式,等解锁的时候,不会只解锁一个线程,而是会将后继的所有的SHARED模式的线程都解锁