AQS 自定义同步锁,挺难的!

AQS是AbstractQueuedSynchronizer的简称。

AbstractQueuedSynchronizer 同步状态

AbstractQueuedSynchronizer 内部有一个state属性,用于指示同步的状态:

private volatile int state;

state的字段是个int型的,它的值在AbstractQueuedSynchronizer中是没有具体的定义的,只有子类继承AbstractQueuedSynchronizer那么state才有意义,如在ReentrantLock中,state=0表示资源未被锁住,而state>=1的时候,表示此资源已经被另外一个线程锁住。

AbstractQueuedSynchronizer中虽然没有具体获取、修改state的值,但是它为子类提供一些操作state的模板方法:

获取状态 protected final int getState() { return state; } 更新状态 protected final void setState(int newState) { state = newState; } CAS更新状态 protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } AQS 等待队列

AQS 等待列队是一个双向队列,队列中的成员都有一个prev和next成员,分别指向它前面的节点和后面的节点。

AQS 自定义同步锁,挺难的!

队列节点

在AbstractQueuedSynchronizer内部,等待队列节点由内部静态类Node表示:

static final class Node { ... } 节点模式

队列中的节点有两种模式:

独占节点:同一时刻只能有一个线程访问资源,如ReentrantLock

共享节点:同一时刻允许多个线程访问资源,如Semaphore

节点的状态

等待队列中的节点有五种状态:

CANCELLED:此节点对应的线程,已经被取消

SIGNAL:此节点的下一个节点需要一个唤醒信号

CONDITION:当前节点正在条件等待

PROPAGATE:共享模式下会传播唤醒信号,就是说当一个线程使用共享模式访问资源时,如果成功访问到资源,就会继续唤醒等待队列中的线程。

自定义同步锁

为了便于理解,使用AQS自己实现一个简单的同步锁,感受一下使用AQS实现同步锁是多么的轻松。

下面的代码自定了一个CustomLock类,继承了AbstractQueuedSynchronizer,并且还实现了Lock接口。
CustomLock类是一个简单的可重入锁,类中只需要重写AbstractQueuedSynchronizer中的tryAcquire与tryRelease方法,然后在修改少量的调用就可以实现一个最基本的同步锁。

public class CustomLock extends AbstractQueuedSynchronizer implements Lock { @Override protected boolean tryAcquire(int arg) { int state = getState(); if(state == 0){ if( compareAndSetState(state, arg)){ setExclusiveOwnerThread(Thread.currentThread()); System.out.println("Thread: " + Thread.currentThread().getName() + "拿到了锁"); return true; } }else if(getExclusiveOwnerThread() == Thread.currentThread()){ int nextState = state + arg; setState(nextState); System.out.println("Thread: " + Thread.currentThread().getName() + "重入"); return true; } return false; } @Override protected boolean tryRelease(int arg) { int state = getState() - arg; if(getExclusiveOwnerThread() != Thread.currentThread()){ throw new IllegalMonitorStateException(); } boolean free = false; if(state == 0){ free = true; setExclusiveOwnerThread(null); System.out.println("Thread: " + Thread.currentThread().getName() + "释放了锁"); } setState(state); return free; } @Override public void lock() { acquire(1); } @Override public void unlock() { release(1); } ... }

CustomLock是实现了Lock接口,所以要重写lock和unlock方法,不过方法的代码很少只需要调用AQS中的acquire和release。

然后为了演示AQS的功能写了一个小演示程序,启动两根线程,分别命名为线程A和线程B,然后同时启动,调用runInLock方法,模拟两条线程同时访问资源的场景:

public class CustomLockSample { public static void main(String[] args) throws InterruptedException { Lock lock = new CustomLock(); new Thread(()->runInLock(lock), "线程A").start(); new Thread(()->runInLock(lock), "线程B").start(); } private static void runInLock(Lock lock){ try { lock.lock(); System.out.println("Hello: " + Thread.currentThread().getName()); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } } 访问资源(acquire)

在CustomLock的lock方法中,调用了 acquire(1),acquire的代码如下 :

public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }

CustomLock.tryAcquire(...):CustomLock.tryAcquire 判断当前线程是否能够访问同步资源

addWaiter(...):将当前线程添加到等待队列的队尾,当前节点为独占模型(Node.EXCLUSIVE)

acquireQueued(...):如果当前线程能够访问资源,那么就会放行,如果不能那当前线程就需要阻塞。

selfInterrupt:设置线程的中断标记

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygwzg.html