Java并发-----AQS(1)

AQS AbstractQueuedSynchronizer同步器,这也是ReentranLock实现锁的基础,如果不知道AQS,就很难理解锁到底是怎么实现的,当然这其中不包括通过synchronize获取的锁,当然我们入手点当然是ReentrantLock,通过ReentranLock lock = new ReentrantLock();

一般来讲我们通过lock.lock()来获取锁,

ReentrantLock
public void lock() {
        sync.lock();
   }

他调用的是同步器的lock方法,所以下面我们看看到底是怎样实现的

同步器及其实例

private final Sync sync;

static final class NonfairSync extends Sync 

public ReentrantLock() {
        sync = new NonfairSync();
    }

所以根据上面的代码可以知道,在默认构造方法里,实例化的非公平同步器NonfairSync,lock里调用的是nonfairSync里面的lock方法,

 
非公平同步器lock方法
final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

 

首先他会通过CAS来吧state从0变成1,如果成功了,就说明以前state的状态为0,由于state是有volatile修饰的,对其他线程是可见的,一修改,别的线程就知道,一旦compareAndSetState(0, 1)返回的是true,意味着其中线程不会执行setExclusiveOwnerThread(Thread.currentThread());知道state变为0,所以执行setExclusiveOwnerThread(Thread.currentThread());是线程安全安全的,而该方法只要是记录当前线程为执行线程。

如果compareAndSetState(0, 1)返回false就会走acquire(1);这个方法是AbstractQueueSynchronizer的

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

首先会判断!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg),首先会执行tryAcquire(1)

这个方法在nonfairSync里面

protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

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;
        }

他会执行nonfairTryAcquire,然后再一次尝试获取锁,当然如果有线程获取锁,这里hi不可能成功的,如果正好有线程执行完,他就能获得锁,不用去等待队列了,然后会判断当前线程是不是正在执行独占锁的线程,如果是就是锁重入,虽然此时state大于0,但是还是会加1,然后返回true,这样的!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)就是false,acquire方法就不会干啥了,如果state != 0,而且此时执行的线程不是当前线程,就会直接返回false,这样的话就会走acquireQueued(addWaiter(Node.EXCLUSIVE), 1);首先他会执行addWaiter(Node.EXCLUSIVE)。

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;
}

把当前线程包装成一个节点,如果当前等待队列的尾节点不为空,并把节点插入等待队列末尾。

在多多线程环境下,就会有两种情况没有解决,一种是首尾节点都为null,另一种是多个线程执行到node.prev = pred;这就会造成多个节点的pred指向尾节点,但是只有一个成功,还有许多没有成功的,这两种情况就会进入enq(node);

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

在这个方法里,就会干两件事,对于首尾节点为空的情况,就会通过一个空节点把首尾节点关联起来,然后进行第二步,第二步就是通过无限循环的情况,把节点插入等待队列的末尾,这样就解决的上述的两种情况。然后把现在尾节点返回。

然后就会执行acquireQueued(node,1)当然,第一个参数就是尾节点,

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);
        }
    }

如果尾节点是首节点的下一个节点,就会再一次的获取锁,如果能获取锁,等待队列就不需要记录该节点的内容,就会把该节点设置为当前首节点,然后把前首节点的引用都设置为空,好让他被jvm回收,然后返回false,

如果当前尾节点不是首节点的下一个节点,就会进入

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);
        }
        return false;
    }

判断当前线程包装的结点的前节点的waitStatus,如果前节点的等待状态是SIGNAL,就返回true,当他的状态大于0,就会把前节点干掉,直接把前节点的前节点和当前线程的包装节点关联起来,如果前面都没有走前面的,就会把前节点的等待状态改为SIGNAL,其实只要前节点的等待状态不为SIGNAL,就会返回false。只要前节点的等待状态为SIGNAL,就进入parkAndCheckInterrupt()

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

把当前线程进入阻塞,返回他的中断标志,如果中断了,就会取消该线程,不会执行。

总结

当一个线程在访问Lock的同步代码块是,首先会去尝试获取锁(CAS把state从0设置为1,如果成功,就会把当前线程设置为独占线程,获取执行权。CAS失败的话就会执行acquire(1),这是线程第一次尝试获取锁),没有获取锁就会执行acquire(1),首先线程会再次尝试获取一次锁tryAcquire(1)(CAS设置state,成功把当前线程设置为独占,如果失败,就会判断是不是重入,重入就会state++,这是第二次尝试获取锁),如果失败就会把他加入到等待队列。

加入到等待队列,当前节点不为空,就会先把当前节点的前节点引用设置为tail,通过CAS把当前节点设置为尾节点。不成功就会执行enq方法,在enq方法里,定义了当head=null的情况,包括了第一次加入节点的情况,他会创造一个空节点来连接首尾节点,同时通过无限死循环来吧节点放进队列里。放进队列的节点不会立刻阻塞。会执行tryqueue(node,1)方法。如果当前的节点的前节点是首节点,就会再一次的获取锁tryacqire()。(这是可能发生的第三次尝试获取锁),成功就返回false,失败的话吧那些没有中断的线程阻塞。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值