理解Java - AQS

1 篇文章 0 订阅

java.util.concurrent中提供了许多同步器,比如常用的ReentranLock,ReentrantReadWriteLock ,Semaphore和CountDownLatch。他们都依赖于AbstractQueuedSynchronizer类提供的处理。

ReentranLock

ReentranLock是重入锁,和Synchronized类似。最大不同是synchronized是基于JVM层面实现的,而Lock是基于JDK层面实现的。

  1. 使用方法:
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();
  1. 涉及到的类:
    在这里插入图片描述
  2. 公平锁和非公平锁
     ReentranLock分为公平锁和非公平锁,区别就在得到锁的机会是否和排队顺序相关。
     如果锁被另一个线程持有,那么申请锁的其他线程会被挂起等待,加入等待队列。先调用lock函数被挂起等待的线程会排在等待队列的前端,后调用的就排在后边。如果此时,锁被释放,需要通知等待线程再次尝试获取锁。
     公平锁会让最先进入队列的线程获得锁。而非公平锁则会在lock时尝试获取锁,所以可能会导致后来的线程先获得了锁,这就是非公平。
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

AbstractQueuedSynchronizer

  AQS 是AbstractQueuedSynchronizer的简称。它提供了一个基于FIFO队列,可以用于构建锁或其他相关同步的基础框架。

  该同步器利用了一个int来表示状态,它提供能够实现大部分同步需求的基础。在多线程环境中对状态的操纵必须确保原子性,因此对于状态的处理,使用同步器提供的以下三个方法对状态进行操作:

java.util.concurrent.locks.AbstractQueuedSynchronizer.getState()
java.util.concurrent.locks.AbstractQueuedSynchronizer.setState(int)
java.util.concurrent.locks.AbstractQueuedSynchronizer.compareAndSetState(int, int)
  1. 加锁
    ReentranLock的lock,直接调用了sync的lock方法(FairSync或NonfairSync的lock)。
    // ReentranLock
    public void lock() {
        sync.lock();
    }
    // FairSync
    final void lock() {
        acquire(1);
    }
    // NonfairSync
    final void lock() {
	    if (compareAndSetState(0, 1))
	        setExclusiveOwnerThread(Thread.currentThread());
	    else
        	acquire(1);
   }

  AQS的acquire方法中,tryAcquire先尝试获取锁,如果得到锁,就正常执行。如果得不到锁,先执行addWaiter给当前线程创建一个节点,并将其加入等待队列,然后acquireQueued尝试获取锁。依然获取不到锁,调用selfInterrupt进行终端处理。

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  1. 尝试获取锁
    tryAcquire在具体的同步器中实现,以FairSync的实现为例,代码如下:
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) { 
        if (!hasQueuedPredecessors() && 
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

  getState取得AQS中的state变量,值为0表示还未被线程占有。hasQueuedPredecessors判断当前阻塞队列上有没有先来的线程在等待(UnfairSync没有这个判断)。CAS乐观锁尝试改变独占性变量,如果成功,那么表示当前线程获得该变量的所有权,也就是获得锁成功。setExclusiveOwnerThread将本线程设置为独占性变量所有者线程。
  如果该线程已经获取了独占性变量的所有权,那么根据重入性原理,将state值进行加1,表示多次lock。
  其他情况都说明获取锁失败。

compareAndSetState函数,使用CAS操作来设置state的值,而且state值设置了volatile修饰符,确保修改state的值不会出现多线程问题。

  1. 加入队列
    addWaiter方法中调用compareAndSetTail尝试将当前线程加入到队列,如果失败在调用enq(更加复杂耗时的算法)加入队列。enq使用for循环,不停的尝试加入队列。
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred; 
        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) { //初始化
            if (compareAndSetHead(new Node())) 
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
  1. 阻塞队列
    acquireQueued会执行一个for循环,直到获取锁,才返回。
    循环中:判断当前节点是否应该获得这个变量(在队首了)?如果是队首,tryAcquire再次尝试获取独占性变量。如果成功,返回false,正常处理(即不用进入阻塞)。如果获取失败,就调用shouldParkAfterFailedAcquire判断是否应该进入阻塞状态。如果当前节点之前的节点已经进入阻塞状态了,那么就可以判定当前节点不可能获取到锁,为了防止CPU不停的执行for循环,消耗CPU资源,调用parkAndCheckInterrupt函数来进入阻塞状态
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); //如果成工,那么就将自己设置为head
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //调用parkAndCheckInterrupt进行阻塞,然后返回是否为中断状态
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL) //前一个节点在等待通知,所以当前节点可以阻塞
        return true;
    if (ws > 0) { //如果前一节点是取消状态,则跳过前一节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //将前一节点的状态设置为signal,返回false,
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this); //将AQS对象自己传入
    return Thread.interrupted();
}
  1. 阻塞和中断
    AQS通过调用LockSupport的park方法来执行阻塞当前进程的操作。
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);//设置阻塞对象,用来记录线程被谁阻塞的,用于线程监控和分析工具来定位
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}
  1. 释放锁
    unlock操作调用了AQS的relase方法来释放独占性变量。如果成功,那么就看是否有等待锁的阻塞线程,如果有,就调用unparkSuccessor来唤醒他们。
    可重入锁的体现,只有等到state的值为0时,才代表锁真正被释放了。所以独占性变量state的值就代表锁的有无。当state=0时,表示锁未被占有,否在表示当前锁已经被占有。
    调用了unpark方法后,被阻塞的线程就恢复到运行状态,就会再次执行acquireQueued中的无限for循环中的操作,再次尝试获取锁。
public final boolean release(int arg) {
    if (tryRelease(arg)) { 
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
    int c = getState() - releases; 
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
private void unparkSuccessor(Node node) {
    .....
     //一般情况下,需要唤醒的线程就是head的下一个节点,但是如果它获取锁的操作被取消,或在节点为null时
     //就直接继续往后遍历,找到第一个未取消的后继节点.
    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;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

整体概念图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值