今日学习
AQS源码全路线+Semaphore路线+基于AQS写自己的Semaphore。深入理解了AQS队列操作和对资源量的把控。昨天电脑没电了。
1. AQS
以下是我自定义一个自己的锁,能实现简单锁逻辑,什么重入中断就没有了啊。
static class MoXiuLock extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
return compareAndSetState(0, 1);
}
@Override
protected boolean tryRelease(int arg) {
return compareAndSetState(1, 0);
}
public void lock() {
acquire(1);
}
public void unLock() {
release(1);
}
}
如果我们需要实现一个独占锁,只要我们重写tryAcquire和tryRelease就可以了,为什么呢? 那我们只需要去看acquire和release做了什么就可以了
public final void acquire(int arg) {
//尝试获取锁如果获取了就直接返回
if (!tryAcquire(arg) &&
//没有获取需要入队
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//响应中断逻辑
selfInterrupt();
}
深入addWaiter
private Node addWaiter(Node mode) {
//创建一个结点
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);
return node;
}
加入队列后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;//返回是否有中断
}
//修改waitState
if (shouldParkAfterFailedAcquire(p, node) &&
//阻塞不会让他一直去for消耗cpu资源
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
深入shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) //当前结点可以行动了
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) { //当前结点需要剔除前一个结点了
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); //他必须把前一个结点的waitStatus弄为SIGNAL才能去阻塞
}
return false;
}
AQS其实就是帮我们完成了入队出队的操作和阻塞线程的操作,这些都是一个并发安全框架必要且类同的东西,所以就有了AQS这个抽象队列同步器帮我们干这些雷同的操作。
public final boolean release(int arg) {
if (tryRelease(arg)) {//这里掉我们自己那个方法
Node h = head;
if (h != null && h.waitStatus != 0) //之前我们不是有结点把前一个结点的waitStatus弄为了SIGNAL这里就派上用了
unparkSuccessor(h);//唤醒下一个线程
return true;
}
return false;
}
AQS呢还是比较简单的,大家也看到了他只有头结点才能获取到锁,这就是他的公平策略了,但如果你是非公平锁的话呢他就只是差了一次队而已。
1. Semaphore 信号量
看以下代码实现了一个简单的自定义信号量
static class MoXiuSemaphore extends AbstractQueuedSynchronizer {
public MoXiuSemaphore(int resource) {
setState(resource);
}
@Override
protected int tryAcquireShared(int arg) {
int state = getState();//拿到当前资源量
if(state == 0) {
return -1;
}
//当前资源数需要减
int nextState = state - arg;
//cas操作实现抢锁
if (compareAndSetState(state, nextState)) {
return nextState;
}
//抢不到的就返回-1了
return -1;
}
@Override
protected boolean tryReleaseShared(int arg) {
for(;;) {
int state = getState();
if (compareAndSetState(state, state+arg)) {
break;
}
}
return true;
}
}
和之前的独占锁不同 这里使用的是都是加了结尾Shared的方法具体的源码读者自己去看吧,其实看懂了AQS的代码的话,这个是非常之简单的。