ReentrantLock 锁机制(非公平锁)源码

ReentrantLock和synchronized一样加锁方式,独占的获取同步的对象锁。

    1. 简介

    ReentrantLock内部由Sync类实例实现其中ReentrantLock.FairSync,ReentrantLock.NonfairSync两个实现,也就是常说的公平锁和不公平锁。

而Sync继承于AbstractQueuedSynchronizer。AbstractQueuedSynchronizer这个类真的很难也很复杂,是构建锁以及实现其他相关同步类的基础框架。本篇文章只能说是对自己看这个类的一点点理解和记录,若有错,请批评指正。

    2. ReentrantLock类的lock()方法

    由于锁Lock的实现都是委托给AbstractQueuedSynchronizer来实现的。因此,就将分析ReenterantLock类中如何获取锁和如何释放锁来理解。

public voidJava.util.concurrent.locks.ReentrantLock.lock()

如果该锁没有被另一个线程保持,则获取该锁并立即返回,并将锁的保持计数器设置为1.

如果当前线程已经保持该锁,则将保持计数加1,并且该方法立即返回。

如果该锁被另一个线程保持,则出于线程调度的目的,禁用该线程,并且在获得锁之前,该线程一直处于休眠状态,此时锁保持计数被设置为1.

    保持计数就是AQS类的state变量。

默认ReentrantLock构造器

public ReentrantLock() {

    sync =new NonfairSync();

}


默认是非公平锁,看看NonfairSync类下的lock方法:

final void lock() {

    if (compareAndSetState(0,1))

        setExclusiveOwnerThread(Thread.currentThread());

    else

        acquire(1);

}

    compareAndSetState(0, 1) 这个是尝试获取锁,把state的状态从0改为1表示取得锁。这个方法原理是CAS,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做!

    setExclusiveOwnerThread()设置获取锁的线程就是当前线程.

    具体调用的是:

protected final boolean compareAndSetState (int expect, int update) {

    return unsafe.compareAndSwapInt (this, stateOffset, expect, update);

}

    unsafe的compareAndSwapInt方法是native的.

但是我们更关注的是,当前线程申请锁不成功的时候是怎么做的.可以看到是AQS中的acquire(1);

public final void acquire(int arg) {

//先尝试获取锁,如果获取锁失败了,acquireQueued则执行

    if( !tryAcquire(arg) && acquireQueued( addWaiter(Node.EXCLUSIVE), arg))

        selfInterrupt();

}

后面的方法。注意&&,前面为true后面才执行。获取锁失败后,会将该线程加入等待队列

    这个看起来比较复杂,我们分解以下4个步骤。

1、如果tryAcquire(arg)成功,那就没有问题,已经拿到锁,整个lock()过程就结束了。如果失败进行操作2。

2、调用addWaiter方法:将当前线程创建一个独占节点(Node)并且此节点加入CHL队列末尾。进行操作3。

3、自旋尝试获取锁,失败根据前一个节点来决定是否挂起(park()),直到成功获取到锁。进行操作4。

4、如果当前线程已经中断过,那么就中断当前线程(清除中断位)。

下面我们对acquire方法中调用的其它方法一一进行分析。

    tryAcquire(acquires)

protected final boolean tryAcquire(int acquires) {

    return nonfairTryAcquire(acquires);

}

final boolean nonfairTryAcquire (int acquires) {

    final Thread current = Thread.currentThread(); 

    int c = getState();  //对于AQS存在一个state来描述当前有多少线程持有锁

    /*

        如果c等于零,则没有线程持有锁,则将锁给当前线程即可

        如果c不等于,说明当前线程已经获取了锁,这里是当前线程再次要获得锁,所以state           要继续+1

    */

    if(c ==0) {

        if(compareAndSetState(0, acquires)) {

            setExclusiveOwnerThread(current);

            return true;

        }

    }

    else if (current == getExclusiveOwnerThread()) { //判断当前线程是否为AQS的独占线程

        int nextc = c + acquires;

        if( nextc < 0 ) // overflow 

            throw new Error("Maximum lock count exceeded");

        setState(nextc);

        return true;

    }

    //获取锁失败,返回false。

    return false;


    tryAcquire的逻辑是这样的, c = getState() 就是当前没有锁竞争的时候,会再尝试去获得锁.

current == getExclusiveOwnerThread()):当前线程已经获取锁了,那么锁的记数加1.

addWaiter(mode)

    如果tryAcquire没有成功,  就执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

    addWaiter是把线程和线程的状态信息封装到一个node对象,加入CHL阻塞链表,Node封装了各种线程状态:

static final int CANCELLED = 1; //这个状态说明该节点已经被取消。

static final int SIGNAL = -1; //这个状态说明该节点后续有阻塞的节点 。

static final int CONDITION = -2; //表明该线程被处于条件队列,就是因为调用了Condition.await而被阻塞

static final int PROPAGATE(-3); //传播共享锁

0:0代表无状态

    其实就是把当前线程放到一个链表的末尾去.具体怎么放有点讲究,而且用到了无限循环,也就是说,一定要把线程放进链表的!

    static final Node EXCLUSIVE = null; //独占节点模式

    static final Node SHARED = new Node(); //共享节点模式

    addWaiter(mode)中的mode就是节点模式,也就是共享锁还是独占锁模式。

privateNode addWaiter(Node mode) {

    //当前线程节点,线程的状态信息封装到一个node对象。

    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;

}

总而言之,addWaiter的目的就是通过CAS把当前现在追加到队尾,并返回包装后的Node实例。

acquireQueued(node,arg)

//这个方法是不断地获取锁,直到成功的获取锁,或者阻塞当前线程

acquireQueued也是个无限循环。就是说要么获取到锁,要么中断当前线程。

1. acquireQueued方法在无限循环内获取前继节点,判断前继节点是否为head,是就再尝试     获取锁,之后前继节点dequeue出队,node成为head。

2. 前继节点p != head 或者 前继节点p == head但是tryAcquire失败了,那么应该阻塞当前线     程等待前继唤醒。阻塞之前会再重试一次,还需要设置前继的waitStaus为SIGNAL。

3. 调用shouldParkAfterFailedAcquire方法,后面的方法是对当前线程进行阻塞并且判断是       否中断。这里注意的是,如果一个线程在等待锁期间这个线程被中断了,这里会将               interrupted赋为true,但是并不return。这个还一直进行for循环,知道这个线程获得了锁,     所以lock()方法不能立即响应中断,必须等线程获得了锁才可以响应中断。对应的可以立       即响应中断的方法为lockInterruptibly()方法。

//阻塞当前线程

private final boolean parkAndCheckInterrupt() {

    //阻塞当前线程

    LockSupport.park(this);

    return Thread.interrupted();


unLock()方法

public void unlock() {

    sync.release(1);

非公平锁release方法


非公平锁tryRelease方法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容