[Java] 简单分析Lock锁lock方法以了解AQS

参考
https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html
https://blog.csdn.net/qq_29373285/article/details/85164190

java提供了两种方式来加锁,一种是关键字:synchronized,一种是concurrent包下的lock锁。
synchronized是java底层支持的,而concurrent包则是jdk实现。
具体流程如下:

什么是AQS

Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。
看一下从Lock类到AQS以及上层的AbstractOwnableSynchronizer继承关系

说明:

  • ReentrantLock实现了Lock接口,Sync、NonfairSync以及FairSync都是其内部类
  • NonfairSync和FairSync是公平锁和非公平锁
  • Sync继承自AQS,这是AQS的核心类
  • 顶层抽象类AbstractOwnableSynchronizer用来保存线程

new ReentrantLock()发生了什么

我们简单的使用lock锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        
        lock.lock();
        
        //TODO: 同步代码块
        
        lock.unlock();
    }
}

看一眼ReentranLock()的构造方法

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

可见,来进行加锁操作的实际上是ReentrantLock中的内部类,且默认是非公平锁

当使用lock.lock()发生了什么

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

可见,也是用sync属性来进行加锁,sync是ReentrantLock中的final属性,那sync是如何加锁的?

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

    /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
    @ReservedStackAccess
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

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

简单说明:

  • compareAndSetState(0,1)这是继承自AQS类的方法,即使用CAS原理修改AQS中的state属性
  • setExclusiveOwnerThread方法指修改成功,即把当前线程用此类存储
  • 如果修改不成功则执行acquire方法

说明:通过修改了AQS的属性state来进行加锁

我们再看看acqure方法

@ReservedStackAccess
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • tryAcquire:会尝试再次通过CAS获取一次锁。

  • addWaiter:将当前线程加入上面锁的双向链表(等待队列)中

  • acquireQueued:通过自旋,判断当前队列节点是否可以获取锁。

具体的addWaiter和acquireQueued方法贴在下面,可以看到CAS原理的确是Java并发的基石

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

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

AQS类中有什么?

通过上述分析,可以看到加锁不成功时,或将当前线程放入AQS的等待队列中,那么这个等待队列是如何定义的呢?

以下为省略的AQS类代码

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    
    protected AbstractQueuedSynchronizer() { }
        static final class Node {

        volatile int waitStatus;

        volatile Node prev;

        volatile Node next;

        volatile Thread thread;

        Node nextWaiter;

        Node() {    // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
    
    private transient volatile Node head;

    private transient volatile Node tail;

    private volatile int state;
    
    //省略代码无数
}

说明:

  • 可以看到AQS其实是一个类似于双向链表的类
  • 其中存储了双向链表的头结点与尾结点
  • 链表的节点是AQS的内部类Node,其thread属性用来保存线程信息

这样的话,可以与之前的addWaiter方法或acquireQueued方法对应,其实就是把为成功加锁的线程加入到双向链表的尾部,以及链表头部的节点自旋加锁的过程。

posted @ 2021-04-09 17:17  herrhu  阅读(221)  评论(0编辑  收藏  举报