深入理解AQS源码解析一

文章目录

        • 一、概念
        • 二、`ReentrantLock`源码分析
          • ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer:
          • Sync又有两个子类(分为:公平锁和非公平锁两类):
          • 首先我们主要先讲一下`ReentrantLock`类中的`lock()`方法和`uplock()`方法都做了些什么事情。
          • 1、lock()方法源码解析
          • 通过上面分析完`lock()`方法以后,我们来看一下`uplock()`方法中源码解析
          • 2、uplock()方法源码解析
        • 三、最后小结一下

一、概念

我们谈到并发,就不得不谈ReentrantLock锁;而谈到ReentrantLock锁,不得不谈一下AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch…。 我们以ReentrantLock作为讲解切入点。

二、ReentrantLock源码分析

ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer:
static abstract class Sync extends AbstractQueuedSynchronizer  
Sync又有两个子类(分为:公平锁和非公平锁两类):
final static class NonfairSync extends Sync
final static class FairSync extends Sync  

显然是为了支持公平锁和非公平锁而定义,默认情况下为非公平锁,设置为公平锁可直接在构造函数中传入true即可。

先理一下Reentrant.lock()方法的调用过程(默认非公平锁)
深入理解AQS源码解析一_第1张图片

首先我们主要先讲一下ReentrantLock类中的lock()方法和uplock()方法都做了些什么事情。
1、lock()方法源码解析
// 首先会进入ReentrantLock中调用lock()方法,接着调用AQS中lock()方法
(1)public void lock() {
     		// 非公平锁	调用无参构造函数
        sync.lock();
    }

// 对所持有资源线程加锁
(2)final void lock() {
     
    		// 这里有两个值 期望值和设置值
            if (compareAndSetState(0, 1))	// 这一步CAS会判断当前锁是否被占用
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);			//	若是锁已持有,则会走这一步 
        }

// 获取锁资源
(3)public final void acquire(int arg) {
     
        if (!tryAcquire(arg) &&		// 尝试获取锁状态失败
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

// 从上一步可以看出,会将当前线程进入等待状态,后续将子线程执行完才会去释放锁资源
(4)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;		// 若是尾节点不为空,则通过CAS设置当前节点为尾节点并返回
        if (pred != null) {
     
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
     
                pred.next = node;
                return node;
            }
        }
        enq(node);		// 否则进入enq()方法
        return node;
    }

// 这个方法会解决两个问题 1、尾节点为空 2、CAS尝试设置锁失败
(5)private Node enq(final Node node) {
     
        for (;;) {
     	// 自旋直到获取到尾节点	
            Node t = tail;
            if (t == null) {
      // Must initialize
                if (compareAndSetHead(new Node())) // 若是尾节点为空,则创建一个新节点
                    tail = head;		// 将父节点设置为尾节点
            } else {
     		// 解决上面CAS设置失败,重新一直尝试去获取
                node.prev = t;
                if (compareAndSetTail(t, node)) {
     
                    t.next = node;
                    return t;
                }
            }
        }
    }

// 从等待队列中去获取锁资源
(6)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;
                }
                // 尝试获取锁失败,会将当前线程挂起,设置状态为SIGNAL,等待其他线程执行完,才会被唤醒
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
     
            if (failed)
                cancelAcquire(node);
        }
  // 将尝试获取锁失败的线程挂起
 (7)private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
     
        int ws = pred.waitStatus;	// 初次进入waitStatus为0,会直接进入else将状态设置为SIGNAL
        if (ws == Node.SIGNAL)		// 若状态为SIGNAL则直接返回
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
     		// 状态为CANCELLED,通过自旋,将是该状态的都会移除
            /*
             * 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);	// 设置状态SIGNAL
        }
        return false;
    }
  
  // 将当前线程阻塞并挂起
 (8)private final boolean parkAndCheckInterrupt() {
     
        LockSupport.park(this);		// 等待线程被唤醒
        return Thread.interrupted();
    }
     
  // 尝试获取锁状态
 (9)protected final boolean tryAcquire(int acquires) {
     
            return nonfairTryAcquire(acquires);	// 使用非公平锁方式获取锁状态
        }
 
 // 非公平锁获取锁状态
 (10)final boolean nonfairTryAcquire(int acquires) {
     
            final Thread current = Thread.currentThread();
            int c = getState();		// 获取state
            if (c == 0) {
     			// 当前state为0,则锁没有被占用,尝试CAS设置锁状态为1
                if (compareAndSetState(0, acquires)) {
     
                    setExclusiveOwnerThread(current);	// 设置线程归当前线程持有
                    return true;
                }
            }
     		// 当出现同一个持有锁的线程,再次获取锁,就会进行锁重入过程
            else if (current == getExclusiveOwnerThread()) {
     	// 这里会进行锁重入
                int nextc = c + acquires;	// state + 1
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);		// 设置锁状态
                return true;
            }
            return false;		// 否则获取锁状态失败,符合FIFO规则
        }
通过上面分析完lock()方法以后,我们来看一下uplock()方法中源码解析
2、uplock()方法源码解析
// 1、首先uplock会去调用AQS里面的release()方法
public void unlock() {
     
        sync.release(1);
    }

// 2、AQS中release()方法
 public final boolean release(int arg) {
     	// arg = 1
        if (tryRelease(arg)) {
     	// 若state状态为0则返回true,否则返回false
            Node h = head;
            if (h != null && h.waitStatus != 0)	// 若头节点不为空且等待状态不为cacelled
                unparkSuccessor(h);		// 将头节点的后续节点唤醒
            return true;
        }
        return false;
    }
// 3、tryRelease()方法在ReentrantLock类中调用
   protected final boolean tryRelease(int releases) {
     	// releases = 1
            int c = getState() - releases;	// 持有锁state = 1,否则为0,所以这里释放之后为0
            if (Thread.currentThread() != getExclusiveOwnerThread()) // 判断当前线程是否是持有锁资源的线程,若不是直接抛异常
                throw new IllegalMonitorStateException();
            boolean free = false;
       		// 如果前面state状态相减之后不为0,则当前线程发生了锁重入
            if (c == 0) {
     
                free = true;
                setExclusiveOwnerThread(null);	// 将线程锁持有者释放并设置为null
            }
            setState(c);	// 重新设置state状态,在这里也有可能发生锁重入
            return free;
        }

// 4、调用AQS中的unparkSuccessor()方法
 private void unparkSuccessor(Node node) {
     
        int ws = node.waitStatus;	// 获取当前节点的等待状态
        if (ws < 0)		// 判断等待状态是否小于0		
            compareAndSetWaitStatus(node, ws, 0);	// 若小于0则将waitStatus设置为0

        Node s = node.next;		// 获取当前节点的下一个节点
     	// 下一个节点为空或waitStatus状态为cancelled
        if (s == null || s.waitStatus > 0) {
     
            s = null;
            // 从尾节点开始寻找;尾节点不为空且尾节点不是当前节点;一直自旋至头节点的下一个节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)		// 并且该节点的状态为SIGNAL
                    s = t;
        }
        if (s != null)		// 判断当前节点的下一个节点是否为空
            LockSupport.unpark(s.thread);	// 若不为空,则唤醒该线程
    }

在这里通过对uplock()源码分析可知:首先会判断当前线程锁持有者是否被释放,若是state = 0,表明锁被释放且获取头节点,判断头节点是否为空且等待状态不为cacelled,则会将头节点中的后续节点唤醒,否则继续会让后续节点挂起状态,等待被唤醒。

三、最后小结一下

1、ReentrantLock锁中最主要的就是lock()方法和uplock()方法,通过对这两个方法源码分析,你可以发现,在并发过程中它是怎么去加锁和释放锁的过程,最主要的就是为什么加锁过程还需要用到CAS方式去获取锁,若是不使用CAS获取锁,有会引发什么问题,有没有更好的解决方案?这个你们可以深入考虑一下。

2、通过源码分析中,我们接触到了公平锁与非公平锁之间获取锁过程中有什么不同?各自都有什么优势,哪这里又引入了一个问题,在你们的理解中什么是公平锁和非公平锁?

3、其实,从ReentrantLock切入点分析过程中,并不是特别难,包括CAS获取锁和加锁的过程以及引入公平锁与非公平锁之间的实现方式,各自的优势体现在哪方面;主要我们还是要去理解去掌握它的有关实现方式,我们可以想一下有没比这种方式更好的实现方案。

结语:后续会继续讲AQS中的CountDownLatch源码解析~

你可能感兴趣的:(java,AQS,多线程,java,多线程,并发编程)