并发编程-AQS应用Lock详解

ReentrantLock

ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁 可以保证线程安全。而且它具有比synchronized更多的特性,比如它支持手动加锁与解锁,支持加锁的公平性。

AQS具备特性

  • 阻塞等待队列
  • 共享/独占
  • 公平/非公平
  • 可重入
  • 允许中断

AQS内部维护属性volatile int state (32位)

  • state表示资源的可用状态

State三种访问方式

getState()、setState()、compareAndSetState()

AQS定义两种资源共享方式

  • Exclusive-独占,只有一个线程能执行,如ReentrantLock
  • Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch

AQS定义两种队列

  • 同步等待队列
    AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先入先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制
    • head 指向的是一个null 线程,tail 指向最后一个线程Node。

在这里插入图片描述

  • 条件等待队列
    Condition是一个多线程间协调通信的工具类,使得某个,或者某些线程一起等待某个条件(Condition),只有当该条件具备时,这些等待线程才会被唤醒,从而重新争夺锁
    在这里插入图片描述

源码解析不可中断Lock()

lock()

final void lock() {
    acquire(1);//关键函数
}

acquire()

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

tryAcquire()

//获取当前线程
final Thread current = Thread.currentThread();
//获取状态数
int c = getState();
//如果是0 说明当前没有线程持锁
if (c == 0) {
		//队列中没有Node
    if (!hasQueuedPredecessors() &&
    	//CAS方式 设置状态值为 acquires
        compareAndSetState(0, acquires)) {
        //设置锁的线程是当前线程
        setExclusiveOwnerThread(current);
        return true;
    }
} // 如果不为0 ,且是当前线程是获取锁的线程,可重入。
else if (current == getExclusiveOwnerThread()) {
    int nextc = c + acquires;
    if (nextc < 0)
        throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
}
return false;
}

addWaiter(Node.EXCLUSIVE) // 由于没有获得锁,添加Node到队列

private Node addWaiter(Node mode) {
//建立节点
  Node node = new Node(Thread.currentThread(), mode);
  //获取尾巴节点
  Node pred = tail;
  if (pred != null) {
      node.prev = pred;
      //CAS 尝试添加节点
      if (compareAndSetTail(pred, node)) {
          pred.next = node;
          return node;
      }
  }
  enq(node);
  return 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(addWaiter(Node.EXCLUSIVE), arg))//阻塞函数

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)) {
            	//如果获取到锁,将当前节点设置为head节点,让原来的head 被gc。
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // p 是当前节点前一个节点。
            /*shouldParkAfterFailedAcquire(p, node) 这个函数是利
            用CAS自旋将pred的WaitStatus 设置为-1.
			*/
            if (shouldParkAfterFailedAcquire(p, node) &&
            	//阻塞
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

源码解析unLock()

unlock()

    public void unlock() {
        sync.release(1);//关键函数
    }

release()

public final boolean release(int arg) {
//尝试解锁,因为重入锁,所以要stack=0 才可以完全解锁
    if (tryRelease(arg)) {
    //如果解锁成功,队列不为空,而且真实首节点状态要为-1
        Node h = head;
        if (h != null && h.waitStatus != 0)
        //接触阻塞状态
            unparkSuccessor(h);
        return true;
    }
    return false;
}

unparkSuccessor()

private void unparkSuccessor(Node node) {

    int ws = node.waitStatus;
    if (ws < 0)
    /*这里会尝试去将head的waitStatus的状态设置为0.个人感觉这个对这个是
    没有关系。因为如果后面会将这个节点抛弃或者重新设置-1。共享锁可能会使
    用,后面继续看源码。
	*/
        compareAndSetWaitStatus(node, ws, 0);

	
    Node s = node.next;
    //如果这个节点是CANCELLED 那么剔除这个节点。并且将唤醒几乎给下一个可唤醒的节点。
    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;
    }
    //如果队列中还有真实线程Node 可以将其唤醒。
    if (s != null)
        LockSupport.unpark(s.thread);
}

可响应中断Lock

lockInterruptibly()

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

acquireInterruptibly(int arg)

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
        //如果线程是被中断不是队列首节点唤醒,抛异常 不会放入到CLH队列中去
    if (Thread.interrupted())
        throw new InterruptedException();
        //若不是被中的 那么尝试获得CHL锁。
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

doAcquireInterruptibly()

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    //创建一个CLH节点并添加到CHL队列中去
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
    //自旋
        for (;;) {
        //获取前一个节点,判断是不是Head之后的节点,也就是第一个可用节点
            final Node p = node.predecessor();
            //如果是首节点,尝试获取锁
            if (p == head && tryAcquire(arg)) {
            //成功,设置这个点为head,
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            //失败
            //设置这个点为等待唤醒的点,并且静如等待状态,后续被唤醒后如果是中断唤醒的,就抛出异常。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
        //如果是中断唤醒的那么会将这个节点删除。并将异常抛出
            cancelAcquire(node);
    }
}

cancelAcquire()

private void cancelAcquire(Node node) {
// 将这个点以及,之前的被标记的CANCELLED 标志的点都会被CHl队列清除出去。
  if (node == null)
      return;
  node.thread = null;
  Node pred = node.prev;
  //一直往前找,直到不是CANCELLED标记的Node
  while (pred.waitStatus > 0)
      node.prev = pred = pred.prev;
//将这个点设置为CANCELLED
  Node predNext = pred.next;
  node.waitStatus = Node.CANCELLED;
  //如果这个节点是尾,将pred后面的都剔除CHL队列
  if (node == tail && compareAndSetTail(node, pred)) {
      compareAndSetNext(pred, predNext, null);
  } else {
      int ws;
      //如果不是尾,也不是首节点 也剔除,
      if (pred != head &&
          ((ws = pred.waitStatus) == Node.SIGNAL ||
           (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
          pred.thread != null) {
          Node next = node.next;
          if (next != null && next.waitStatus <= 0)
              compareAndSetNext(pred, predNext, next);
      } else {
      //首节点 在unparkSuccessor 也会剔除
          unparkSuccessor(node);
      }

      node.next = node; // help GC
  }
}
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页