java多线程高级-AQS(三)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/piaoslowly/article/details/81562238

java多线程高级-AQS(三)

前面第一章介绍了锁的结构,现在介绍java里面的java.util.Concurrent包的里面的基石,AQS。

什么是AQS?

AbstractQueuedSynchronizer(后面简称AQS)AQS是JDK1.5提供的一个基于FIFO等待队列实现的一个用于实现同步器的基础框架,这个基础框架的重要性可以这么说,JUC(java.util.Concurrent)包里面几乎所有的有关锁、多线程并发以及线程同步器等重要组件的实现都是基于AQS这个框架。AQS的核心思想是基于volatile int state这样的一个属性同时配合Unsafe工具对其原子性的操作来实现对当前锁的状态进行修改。当state的值为0的时候,标识改Lock不被任何线程所占有。

AQS源码分析

AQS大致实现流程图

图说明:

private Lock lock = new ReentrantLockSource();  
public void serviceA() {
    lock.lock();
    ....
    lock.unlock();
}

当执行lock.lock()时,就发生了我们图中的关系图了。

假如现在有两个线程(线程1,线程2)同时执行到了lock()方法了,线程1,2开始到了NonfairSync,做了一次尝试获取锁(CAS)明显有且只有一个线程能获取到锁,开始设置lock对象的全局变量,保证不让其它线程往下执行。
假如线程获取到了,1开始执行lock()方法后面的代码;线程2开始进入下一步中。

线程1:cas获取成功,往下执行代码;
线程2:cas失败,开始进入创建队列慢慢路;线程2开始下面几步创建等待队列

  • 在创建node节点的时候看看线程1释放了锁没有,释放了,那接着往下执行,就不创建队列;
  • 线程1还是没有释放锁,开始创建node节点,查看是否有队列了,没有创建队列,然后跟在队列头后面;如果有队列了,就跟在队列最后面;然后返回node节点。
  • 开始检查队列中是否有node可以获取锁了,可以则当前线程可以跳出无线循环,往下执行代码了。当前队列没有一个获取锁的,则无限循环继续查找是否有node可以获取线程。(每个线程都在无限循环,当head节点指向自己线程的node时就可以跳出循环了)

开始竞争执行权

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            //线程1先进入尝试设置状态,如果成功则获取到执行权
            //线程1获取锁,设置状态=1;
            if (compareAndSetState(0, 1)) {
                //标示当前已经获取了锁。
          setExclusiveOwnerThread(Thread.currentThread());
            } else
                //线程2没有获取锁,需要做一些列事:创建队列或者追加队列后面;阻塞自己,不在往下继续执行。
                acquire(1);//第二个线程开始尝试获取锁
        }

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

compareAndSetState是一个本地方法CAS操作,线程1,线程2只会有一个成功,成功的继续执行lock后面的代码,失败的进入等待队列。

acquire创建node

创建队列之前,再次尝试是否能获取执行权力,看看线程1是否已经执行完毕了。

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

tryAcquire-> nonfairTryAcquire

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            System.out.println("nonfairTryAcquire没有锁了哦:" + current.getId());
            int c = getState();//getstate是volatile,
            System.out.println("再次获取锁状态,看看有没有机会获取锁c:" + c);
            if (c == 0) {
                //state对线程2具有可见性,线程2拿到最新的state,再次判断一下能否持有锁(可能线程1同步代码执行得比较快,这会儿已经释放了锁),不可以就返回false。
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //判断当前是否是自己,如果是第一个线程再次调用了lock方法,nextc状态+1,这就是可重入了
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow//Integer.MAX_VALUE次,也就是2147483647,超过integer最大值会变负数
                    //这也说明了线程锁可重入的最大次数
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//状态+1,并不会再次加锁,这就是可重入了。
                return true;
            }
            return false;
        }

进入等待队列之前再次cas一次,看看线程1是否已经执行完毕了,那线程2就可以直接执行了,而不用进入等待队列。

开始创建等待队列

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        System.out.println("addWaiter:创建等待队列:waitStatus" + node.waitStatus);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;//node节点指向最后一个节点
        if (pred != null) {
            System.out.println("tail不等于空哦:" + pred.thread.getId());
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                System.out.println("new node追加到队列尾巴");
                return node;
            }
        }
        System.out.println("队列为null,创建新的队列");
        //创建一个新的队列
        enq(node);
        return node;
    }

创建等待之前先判断是否已经有队列了,有则最佳到队尾。没有则开始enq

真正创建队列了
enq

private Node enq(final Node node) {
        //这里使用了无限循环,配合下面的compareAndSet**方法的使用。
        //compareAndSet**方法里面是一个CAS,如果设置失败了,就返回false,
        //如果CAS返回false了,就在循环一直循环直到创建成功为止。
        for (; ; ) {
            System.out.println("无限循环中.......");
            Node t = tail;
            if (t == null) { // Must initialize
                //如果没有节点,开始使用cas方式创建一个head节点 
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //node的上一个节点指向了t
                node.prev = t;
                //如果节点存在,则追加到队列的下一个节点。
                System.out.println("队列终于不为null了");
                if (compareAndSetTail(t, node)) {
                    //t的下一个队列指向了node;这是一个双向队列
                    t.next = node;
                    System.out.println("开始创建把节点追加到队列尾巴去,并返回节点");
                    return t;
                }
            }
        }
    }

等待队列acquireQueued

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        System.out.println("acquireQueued:新创建的等待节点,开始加入到队列里面了");
        try {
            boolean interrupted = false;
            //无限循环,线程2被锁了,一直在等待线程1释放锁
            for (; ; ) {
                final Node p = node.predecessor();
                //线程2尝试能否再次获取锁
                if (p == head && tryAcquire(arg)) {
                    //线程2获取到了锁,线程2的node将成为队列头
                    System.out.println("获取锁后开始称为称为对头threadid:" + node.thread.getId());
                    setHead(node);
                    //丢去之前的队列头,这就是一个线程一个节点(第一个获取锁的线程不在队列里面),线程执行完毕后就把节点删除
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //shouldParkAfterFailedAcquire设置线程等待,
                //parkAndCheckInterrupt阻塞线程2停止往下执行;(这里是有2个线程,假设线程1先获取了锁,线程2执行lock时的内部操作)
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt()) {
                    interrupted = true;
                    System.out.println("加锁了,线程被挂起了。");
                }

            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

unlock取消锁

public final boolean release(int arg) {
        //尝试解锁
        //tryRelease只是把各种状态还原到初始化之前而已。
        if (tryRelease(arg)) {
            System.out.println("解锁成功,开始指向下一个节点,就是下一个线程获取锁的时候了");
            Node h = head;//指向了线程2的node
            //真正的解锁是在这里,线程1先获取了执行权,线程1可以执行;线程2才是被那个lock住的,所以这里开始唤醒线程2了。
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease开始尝试解锁

protected final boolean tryRelease(int releases) {
            //getState()-1,和前面的setState对应,就是说线程1加了多少锁,就需要解多少锁。
            int c = getState() - releases;
            System.out.println("tryRelease开始解锁了c=" + c);
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

setStatus+1,加了几次也需要-1几次,直到为0时才为真正解锁。

tryRelease尝试成功后开始解锁 -> unparkSuccessor

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.
         */
        //等待时被设置为-1了,
        int ws = node.waitStatus;
        System.out.println("unparkSuccessor-ws:" + ws);
        //现在获得锁了,把自己的状态置为0
        if (ws < 0)
            compareAndSetWaitStatus(node, 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 t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //线程2不为空,解锁
        if (s != null)
            LockSupport.unpark(s.thread);
    }

tryLock尝试获取锁

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;
            }
            //没有能获取到锁,直接返回false,不做任何操作。
            return false;
        }

tryLock的源码已经很清楚了,只是尝试看看自己能否获取锁,能获取就获取并返回true,不能获取就直接返回false,没有获取锁的时候并不会把线程加入到等待队列里面。所有tryLock只是在能获取锁的时候获取锁。

加锁解锁总结

1.加锁解锁过程是这样的

  • 线程1执行lock时,获得了执行权,就开始执行了。
  • 线程2执行lock时,发现获取不到执行权,就开始进入等待队列,并且把自己的当前线程2挂起。
  • 线程1执行完后,调用unlock,unlock把线程2从加锁队列中解锁出来。

所以说加锁时把自己给锁住,解锁是有执行权限的线程去解救被锁住的线程。

2.从上面的代码也可以看出,AQS中每操作一步遇到线程竞争的过程都是采用CAS的无锁操作的(没有使用synchronized)

参看文献

http://www.cnblogs.com/xrq730/p/4979021.html

展开阅读全文

没有更多推荐了,返回首页