ReentrantLock和AQS源码解析

建议去B站先看下子路老师的视频

ReentrantLock源码

  • synchronized通过在对象头的markword进行操作从而实现互斥锁
  • ReentrantLock通过将线程加入AQS阻塞队列从而实现同步互斥锁

首先初始化一个ReentrantLock

ReentrantLock lock = new ReentrantLock(true);

这个时候是默认的构造函数,新建一个公平锁

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

然后加锁操作lock.lock(),这个方法会去调用FairSync下面的lock()方法

final void lock(){
    acquire(1);
}

接着我们进入这个acquire(1)去看看,原来它是AQS的一个方法

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

首先第一个判断是tryAcquire(arg),我们应该去它的子类,也就是Sync去看,这个时候也就是去FairSync类的tryAcquire(arg)去看

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

AQS源码(自旋+CAS+park/unpark)

AQS(AbstractQueuedSynchronizer)类的设计主要构成

// 队首
private transient volatile Node head;
// 队尾
private transient volatile Node tail;
// 锁状态
private volatile int state;
// 持有锁的那个线程(这个属性其实是在AbstractOwnableSynchronizer类里的)
private transient Thread exclusiveOwnerThread;

AQS中的入队操作源码解析

addWaiter
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使第一个线程入队
	enq(node);
    return node;
}
enq
// 通过这个函数我们可以看出AQS中的head一直都是指向一个空的Node节点
private Node enq(final Node node) {
    // 若当前队列为空,那么for循环会执行两次,第一次执行if(将head指向一个空的Node),第二次执行else(将当前的node连接到上个空Node之后,也就是入队)
	for (;;) {
		Node t = tail;
        // 第一次会执行if(将head指向一个空的Node)
		if (t == null) { // Must initialize
			if (compareAndSetHead(new Node()))
				tail = head;
		}
        // 第二次会执行else(将当前的node连接到上个空Node之后,也就是入队)
        else {         
            // 连接到上一个空Node,即入队
        	node.prev = t;
            // 将队列尾节点指向当前节点node
            if (compareAndSetTail(t, node)) {
            	t.next = node;
            	return t;
            }
		}
	}
}

入队后的队列结构如下图所示:注意,队列头为空节点,代表的是当前获得锁的线程。也可以理解为持有锁的线程永远不会在队列里(保证持有锁的线程不参与排队的原则),比如这个时候t1拿到锁了,那么t1会被置位head,并且里面的值全为null(见acquireQueued方法中的setHead方法)

多线程非交替执行下。比如t1线程获取了ReentrantLock锁,这个时候t2线程来了,对t2的处理流程图如下图所示:

总结

ReentrantLocksynchronized快,是因为前者的加锁操作是在JDK的工作层次下操作的,而后者需要调用操作系统去工作。

  • ReentrantLock中一部分操作是在JDK的层面下解决的,还有一部分也是在操作系统中解决的;
    • 多线程交替执行(不会交叉,比如线程t1执行完了t2才开始执行,这个时候和队列没有关系):比如上面贴出的AQS中的acquire方法,首先是执行tryAcquire这个方法是不会调用操作系统的,如果这个方法返回true了,那就不会再继续判断后面调用操作系统的的acquireQueued方法
    • 多线程不是交替执行的:这个时候就会AQS中的队列就不为空了。

synchronized在JDK1.6之前全都要经过操作系统来解决,所以那个时候提出了ReentrantLock

ReentrantLock.lockTnterruptibly()

if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
原文地址:https://www.cnblogs.com/flyingrun/p/13740443.html