`
碧海山城
  • 浏览: 192896 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

AQS:ReentrantLock源码分析

 
阅读更多

 

接着上一篇的

 

关于java线程(4)----JUC之 AQS 状态依赖的抽象

看一下ReentrantLock的源码,这里只是从AQS的角度出发,并不是从Lock的角度来看,那个以后再分析把

 

 

AQS的状态角度,代码整体结构上是这样的:

 

//检查状态
while(!checkAndChangeState){
	enque(currentthread)  //将当前线程加入等待队列
	park(currentThread)	//挂起当前线程
}
//do sth
…….

//释放锁,恢复状态
changeState(){
	Dequeue(current)	//出对
	Unpark(queue.thread)//唤醒其他等待中的线程
	
}

 

 和真正的代码比起来主要是一些小地方的优化,比如一些自旋操作,特别是对于很小范围内的锁,另外,就是队列中的线程可能是已经取消的,这也要做相应的处理,下面就具体分析下

 

Lock的结构体系成对的主要lockunlock其他方法到时候再看把:


 我们看下lock方法:

 

public void lock() {
        sync.lock();
}
//先暂且不管公平锁还是非公平锁,从独占锁的角度来说,就是请求一个状态
final void lock() {
            acquire(1);
}
 

看下acquire的代码,完全就是上面伪代码的结构

 

public final void acquire(int arg) {
		//判断状态是否ok(自己实现),不ok的话就加入队列,并且阻塞(父类实现)
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}

 

再来看下状态判断,这个是需要每个synchronizer自己做的:

 

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
//这里初看起来会有并发问题,但是下面的cas操作保证了不会有并发问题,并且state字段是volatile的
            if (c == 0) { 
                if (compareAndSetState(0, acquires)) { 
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
	//重入锁,单个线程内,不会有并发性的,所以直接set就好
                setState(nextc);                 
	return true;
            }
            return false;
        }
 

这个方法如果返回true的话,代表条件允许,这样线程也就获得到锁了,其他啥也不用做了,等着释放锁,但是如果返回false,表示其他线程占用着,就需要加入等待队列,并且阻塞线程:

 

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

 

先看addWaiter

 

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;
            }
        }
        //队列为空,或者存在高并发,入队的时候失败了,需要用while尝试入队
	enq(node);     
        return node;
	}

   // 一个完整的入队操作,需要在一个循环里面判断队列是否为空、高并发等情况

 

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // 队列为空,需要初始化
                Node h = new Node(); // Dummy header
                h.next = node;
                node.prev = h;
                if (compareAndSetHead(h)) {//设置队列头,原子操作
                    tail = node;
                    return h;
                }
            }
            else {// 队列非空,直接插到最后面

                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

 

//在入队之后,在看下真正阻塞线程的acquireQueued,这里是一个循环操作,唤醒的时候也是重新去竞争,不是立刻获得锁
final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
//在真正阻塞线程之前,如果发现自己前面的节点是head,那还要再尝试下去获取锁,获取到以后就把head踢了,自己当head,因为head的意义表示正在占用锁的节点(某些同步范围很短的锁很有用)                
if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
//该方法判断是否需要阻塞当前线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt()) //park当前线程
interrupted = true;        //默认不处理interrupted
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);   //发送异常的情况下,取消当前节点,并且如果当前节点是head,或者当前节点是等待唤醒状态,那么,还要尝试唤醒后面的节点
            throw ex;
        }
}

 

	//决定是否需要阻塞当前线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int s = pred.waitStatus;
//如果当前节点的前一个节点的状态小于0(signal/condition),表示前面那个节点也在等待唤醒,果断把自己挂起
        if (s < 0)
            return true;
//如果前面节点的状态大于0,(cancelled),表示前面的线程已经取消,向前遍历,直到找到状态不大于0的节点 
        if (s > 0) {        
	   		  do {
			 	node.prev = pred = pred.prev;
	  	  	 } while (pred.waitStatus > 0);
	  		 pred.next = node;
		} else 
			/*
	* 前面一个节点的状态为0,那么就是前一个节点认为他后面没有节点需要唤醒	* 啦,这时候要果断把他的状态改为SIGNAL,因为SIGNAL状态表示后面有阻塞      
	线程需要唤醒. 
             */
            compareAndSetWaitStatus(pred, 0, Node.SIGNAL);

	/**
	在state>=0的情况下,会走到这里,这里不能返回true把自己挂起,
	因为这时候线程切换,占用锁的线程A已经结束(并且发出了unpark信号),如果这时候线程B直接返回true阻塞自己,那可能会因为错失信号B永远无法唤醒;返回false,当前线程会去再次尝试获取锁,如果还是不能获取,则阻塞;
	*/
        return false;
	}
	//挂起当前线程,因为park会相应中断,但是不是抛出异常,因此这里将是否中断作为boolean类型返回,交给外部代码处理
	 private final boolean parkAndCheckInterrupt() {
	        LockSupport.park(this);
	        return Thread.interrupted();
	//并不是只有unpark和interrupt才能唤醒他,参考前面的关于LockSupport的讲解;唤醒的线程,重新去竞争锁,在高并发的情况下,有可能另一个节点正好也在请求lock,那么他刚好tryAcquire成功了,则当前线程又会重新阻塞!
	    }
 

 

 释放锁

 

/**
     *  释放锁主要做下面两件事情:
	 *	1.修改状态,改为可以获取锁
	 *  2.如果有等待的线程,则唤醒一个
     */
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
			//如果waitStatus为0,则表示后面没阻塞线程了,没必要进行唤醒了
            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) {
		/*
         * 把自己状态改为0,表示后面没有节点需要唤醒;如果后面有需要唤醒的节点,在请求锁的时候会把他的状态改为SIGNAL,不是很明白为什么这么做,难道是一种小小的优化?
         */
        int ws = node.waitStatus;
        if (ws < 0)
			compareAndSetWaitStatus(node, ws, 0);
        //取队列里面状态不大于0(cancelled)的节点,如果大于0,则从队列移除,然后从后往前找,找到最前面的一个非cancelled节点,并且唤醒这个节点
        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);
}
 

 

总结

1.通过AQSstate字段来表示锁是否被占用,特别需要注意的就是他的操作都是原子的

2.通过队列来保存阻塞的线程

3.通过LockSupport来进行挂起和唤醒操作(LockSupport的注意点可以看上一篇)

 

 

从挂起的时机上,AQS加了一些自旋操作,不是每次发现不能获取锁就挂起,而是后来会再次尝试获取锁,这样,对那些同步范围非常小,时间非常短的锁,应该是一种性能的提高。

 

另外,对于队列的操作,从我个人的角度来看,每次取当前正在跑的节点,跑完以后再取下一个节点,如果是取消状态,那么再去下一个,最后找到一个ok的唤醒,这样就可以了。不过AQS增加了一些状态判断,比如通过waitStatus来判断后面是否有节点需要唤醒,从角度来看,这完全增加了成本,不过性能提高多少就难说喽!

 

2
5
分享到:
评论
1 楼 zhongliangjun1 2012-12-14  
写得非常好,受益良多!

相关推荐

    7、深入理解AQS独占锁之ReentrantLock源码分析(1).pdf

    ### 三、ReentrantLock源码分析 #### 3.1 ReentrantLock介绍 ReentrantLock是一种基于AQS框架实现的可重入锁。它可以显式地控制锁的获取和释放,并提供了公平性和非公平性两种获取策略。此外,ReentrantLock还支持...

    ReentrantLock源码分析

    ### ReentrantLock源码分析 #### 一、ReentrantLock简介 ReentrantLock是一个基于`AbstractQueuedSynchronizer`(AQS)实现的高级锁工具类。与传统的synchronized关键字相比,ReentrantLock提供了更多控制手段,比如...

    AQS源码分析 (1).pdf

    接下来,我们来具体分析一下AQS的源码。AQS中定义了一个名为state的volatile变量,用于表示同步状态。这个变量有三种操作方法:getstate()、setstate()和compareAndSetState(),分别用于获取、设置和原子性地更新...

    Java并发系列之ReentrantLock源码分析

    Java并发系列之ReentrantLock源码分析 ReentrantLock是Java 5.0中引入的一种新的加锁机制,它实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。ReentrantLock的底层实现是通过AQS来实现多线程同步...

    Java 多线程与并发(11-26)-JUC锁- ReentrantLock详解.pdf

    **源码分析** ReentrantLock类实现了Lock接口,提供了lock()、unlock()等方法。Sync类是内部抽象类,继承自AQS,它有两个子类NonfairSync和FairSync。Sync类中有lock()抽象方法,以及nonfairTryAcquire()和...

    第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()1

    7. **源码分析** 在深入源码之前,我们需要对获取锁的整体流程有一个大致的理解。ReentrantLock的`lock()`方法调用`Sync`的`lock()`,然后由`NonfairSync`或`FairSync`完成具体的锁获取逻辑。这个过程中涉及到的...

    Java并发编程:深入解析抽象队列同步器(AQS)及其在Lock中的应用

    维护资源状态的可用性最后,文档提供了AQS源码的初步分析,突出了其设计和实现的关键部分,如等待队列节点类Node的定义综合来看,文章为Java开发者提供了对AQS及其在ReentrantLock中应用的详细理解,是探索Java并发...

    7 AQS源码分析.docx

    本文将详细分析AQS的源码,探讨其工作机制,以及在Java中如何实现不同类型的锁。 首先,我们需要了解锁的基本类型。在Java中,锁主要分为两类:悲观锁和乐观锁。悲观锁认为并发操作会导致数据不一致,因此在操作...

    Java并发包源码分析(JDK1.8)

    Java并发包源码分析(JDK1.8):囊括了java.util.concurrent包中大部分类的源码分析,其中涉及automic包,locks包(AbstractQueuedSynchronizer、ReentrantLock、ReentrantReadWriteLock、LockSupport等),queue...

    JUC AQS(AbstractQueuedSynchronizer)

    ReentrantLock Lock 加锁过程源码分析图,AQS 源码分析

    图灵Java高级互联网架构师第6期并发编程专题笔记.zip

    09-深入理解AQS之独占锁ReentrantLock源码分析-fox 10-深入理解AQS之Semaphorer&CountDownLatch&CyclicBarrie详解-fox 11-深入理解AQS之CyclicBarrier&ReentrantReadWriteLock详解-fox 12-深入理解AQS之...

    基于JDK源码解析Java领域中的并发锁之设计与实现.pdf

    通过以上分析,我们可以看到Java并发包提供了丰富的并发锁机制,从简单的synchronized关键字到复杂的AQS、LockSupport、Condition等,这些都是为了解决并发编程中的互斥和同步问题。在实际应用中,开发者需要根据...

    Java并发 结合源码分析AQS原理

    Java并发结合源码分析AQS原理 Java并发编程中,AQS(AbstractQueuedSynchronizer)是一个核心组件,它提供了一个基于FIFO队列和状态变量的基础框架,用于构建锁和其他同步装置。在这篇文章中,我们将深入探讨AQS的...

    Java并发系列之AbstractQueuedSynchronizer源码分析(条件队列)

    在本篇中,我们将深入分析AQS的条件队列,它是实现高级同步机制如`ReentrantLock`和`CountDownLatch`的关键部分。 条件队列是AQS中与`Condition`接口相关的部分,它允许线程在满足特定条件时等待,而不是简单地阻塞...

    AQS的底层原理.zip

    本文将通过图像解析和源码分析,深入探讨AQS的工作机制。 一、AQS基本结构与原理 AQS的核心是一个int类型的state字段,它表示资源的状态。当state为0时,表示资源可获取;非0则表示已被占用。AQS维护了一个FIFO的...

    带你看看Java-AQS同步器 源码解读四 条件队列Condition上

    源码分析中,我们可以看到`Condition`的`await()`方法会使得当前线程释放锁、加入条件队列并进入等待状态;而`signal()`方法会唤醒条件队列的第一个线程,使其有机会重新竞争锁。这些操作都确保了线程安全性和正确性...

    ReentrantLock代码剖析之ReentrantLock_lock

    在本文中,我们将深入分析`ReentrantLock`的`lock()`方法,理解其内部机制,包括锁的获取、释放以及公平性和非公平性的实现。 首先,`ReentrantLock`的`lock()`方法很简单,它只是调用了内部类`Sync`的`lock()`方法...

    Java源码解析之可重入锁ReentrantLock

    通过对ReentrantLock的源码分析,我们可以更好地理解ReentrantLock的实现机制和使用场景,从而更好地应用于实际开发中。 知识点: 1. ReentrantLock是一个可重入锁,它和synchronized的方法和代码有着相同的行为和...

    带你看看Java的锁(一)-ReentrantLock

    带你看看Javad的锁-ReentrantLock前言ReentrantLock简介Synchronized对比用法源码分析代码结构方法分析SyncNonfairSyncFairSync非公平锁VS公平锁什么是公平非公平ReentrantLockReentrantLock的构造函数lock加锁方法...

Global site tag (gtag.js) - Google Analytics