主要分析下ReentrantLock锁的占用和释放过程。

一.几个核心变量

AbstractOwnableSynchronizer{
     /**
     * 表示当前占有独占锁的线程,为null时说明锁未被占用
     */
    private transient Thread exclusiveOwnerThread;
}
AbstractQueuedSynchronizer extend AbstractOwnableSynchronizer{
    private transient volatile Node head;//队列首节点
    private transient volatile Node tail;//队列尾节点
    private volatile int state;//同步状态,表示锁是否被占用。可重入锁,占用锁时继续获取锁,state=2
}

/**
 * waitStatus:
 *1:线程取消等待
 *-1:后继节点的线程处于等待状态,需要当前结点唤醒
 *-2:等待condition,condition.signale()唤醒,该线程会加入到队列中等待获取锁
 *-3:下一次共享式同步状态获取将会被无条件地传播下去??没看懂
 */
static final class Node {
    volatile int waitStatus;//当前线程的等待状态。状态被一个线程修改后,立即对其他线程可见
    volatile Node prev;//前置节点
    volatile Node next;//后置节点
    volatile Thread thread;//节点所属线程
}

AbstractQueuedSynchronizer同步控制核心类,核心变量为state,state=0表示当前锁被占用,state!=0表示锁被占用,exclusiveOwnerThread变量表示当前占用锁的线程,若为null,表示锁未被占用。

二.线程获取锁的流程

1.尝试获取锁,若获取失败,则添加到待占用锁队列,中断当前线程等待占有锁后继续运行

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

2.获取失败的锁加入等待队列,第一个节点Node0为头节点,第二个节点Node1才是链表第一个个数据节点,存储有效数据信息。

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

    //死循环添加节点,返回node的前置节点
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //尾节点不存在,初始化。设置一个前置节点node0,即为头节点也是尾节点
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //尾节点存在,即链表有效,将新node添加到尾部
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

3.死循环获取锁。死循环,只有前置节点为头节点的链表节点,即链表的第一个数据节点尝试获取锁

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            //前置结点为头结点,即node为第一个,设置当前节点为头节点
            if (p == head && tryAcquire(arg)) {
                //将当前节点设置为头结点,移除之前的头节点
                setHead(node);
                p.next = null; // help GC。。p节点断开和链表的连接
                failed = false;
                return interrupted;
            }
            //前置节点非首节点,则当前线程中断
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())//阻塞线程并判断线程是否中断
                interrupted = true;
        }
    } finally {
        if (failed)
        cancelAcquire(node);
    }
}

4.获取锁的具体流程

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //当前锁未被占用,且当前线程是队列中头元素Node1,如果是的话,则获取该锁,设置锁的状态,并设置锁的拥有者为当前线程
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //若当前线程占有锁,锁可重入,state+1.
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

三.释放锁的流程

1.尝试释放锁,锁释放成功,则唤醒下一个节点

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        //若锁释放成功,则唤醒当前结点的后继结点
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

2.如何释放锁

每次释放state-1,并更新线程state值,直到state减到0,该线程释放锁成功。并将exclusiveOwnerThread字段置为null,表示当前未有线程占有锁

protected final boolean tryRelease(int releases) {
    //获取当前的state值,state-1
    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;
}

3.如何唤醒下一个结点

private void unparkSuccessor(Node node) {
    //当前结点的等待状态为置为0,Node1
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //从尾结点向前查找第一个waitStatus小于0的Node,Node2
    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);
}

四.公平锁和非公平锁

ReentrantLock有可以作为公平锁和非公平锁。默认非公平锁。

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

公平ReentrantLock锁获取锁

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

非公平ReentrantLock锁获取锁

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

二者的区别就是非公平锁获取锁的时候首先判断锁是否被占用,若没有被占用,直接占有锁,否则加入等待队列。

公平锁获取锁的时候的直接加入等待队列。等待队列的线程满足FIFO的条件,即先进入队列的线程会先获取锁。

ReentrantLock分析的更多相关文章

  1. ReentrantLock 分析

    带着疑问去分析 ReentrantLock是如何实现锁管理的. ReentrantLock是如何实现重入的. ReentrantLock是如何实现公平锁与非公平锁. ReentantLock的公平锁为 ...

  2. 透过 ReentrantLock 分析 AQS 的实现原理

    对于 Java 开发者来说,都会碰到多线程访问公共资源的情况,这时候,往往都是通过加锁来保证访问资源结果的正确性.在 java 中通常采用下面两种方式来解决加锁得问题: synchronized 关键 ...

  3. java源码-ReentrantLock源码分析-2

    继续上篇ReentrantLock分析如何唤醒线程: 当调用lock.unlock()方法最终调用AQS类中的release方法,开始释放锁 tryRelease(1)方法在Sync对象中实现,首先会 ...

  4. ReentrantLock和condition源码浅析(一)

    转载请注明出处..... 一.介绍 大家都知道,在java中如果要对一段代码做线程安全操作,都用到了锁,当然锁的实现很多,用的比较多的是sysnchronize和reentrantLock,前者是ja ...

  5. JUC源码学习笔记1——AQS和ReentrantLock

    笔记主要参考<Java并发编程的艺术>并且基于JDK1.8的源码进行的刨析,此篇只分析独占模式,后续在ReentrantReadWriteLock和 CountDownLatch中 会重点 ...

  6. java总结

    JUC概况 以下是Java JUC包的主体结构: ? Atomic : AtomicInteger ? Locks : Lock, Condition, ReadWriteLock ? Collect ...

  7. Android 技能图谱学习路线

    这里是在网上找到的一片Android学习路线,希望记录下来供以后学习 1Java 基础 Java Object类方法 HashMap原理,Hash冲突,并发集合,线程安全集合及实现原理 HashMap ...

  8. ThreadPoolExcutor 原理探究

    概论 线程池(英语:thread pool):一种线程使用模式.线程过多会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务.这避免了在处理短时间 ...

  9. 深入剖析AQS

    目录 摘要 AbstractQueuedSynchronizer实现一把锁 ReentrantLock ReentrantLock的特点 Synchronized的基础用法 ReentrantLock ...

随机推荐

  1. 如何为 caddy 添写自定义插件

    如何为 caddy 添写自定义插件 项目地址:https://github.com/yhyddr/quicksilver/tree/master/gosample/caddy-plugin 前言 Ca ...

  2. 「求助」关于MacOS 适配不了SOIL的问题 以及我自己愚蠢的解决办法

    我的环境 macOS High Sierra 10.13.6 (2018) 我的SOIL源是通过 终端 git clone https://github.com/DeVaukz/SOIL 直接从gay ...

  3. DT-06 For Homekit

    一. 配置DT-06上网 连接此热点,会自动弹出wifi配置页面. 输入选中的路由密码,点 Join加入,如果路由没有出现在列表中,点 Other手工输入(仅支持2.4g路由配置) 二.配置dt-06 ...

  4. Intellij IDEA 2019.2 设置Git

    点击File选择Settings 查找Git 选择Git安装路径bin目录下git.exe并点击text是否可用 选择OK安装完成. IDEA 拉去github 或 gogs上的项目(选择VCS —— ...

  5. React中控制台警告

    1.dll_lib.js:1 Warning: bind(): You are binding a component method to the component. React does this ...

  6. IDEA搭建工程

    1. 创建一个Project File -> New -> Project...   : 选择jdk版本,然后Next: 输入项目名,确定项目路径,Finish. 2. 创建一个Modul ...

  7. ASP.NET Core[源码分析篇] - WebHost

    _configureServicesDelegates的承接 在[ASP.NET Core[源码分析篇] - Startup]这篇文章中,我们得知了目前为止(UseStartup),所有的动作都是在_ ...

  8. .net测试篇之测试神器Autofixture Generator使用与自定义builder

    有了上一节自定义配置,很多问题都能解决了,但是如果仅仅是为了解决一个简单问题那么创建一个类显得有点繁重.其实AutoFixture在创建Fixture对象时有很多方便的Fluent配置,我们这里介绍一 ...

  9. 掌握 Maven 私服

    前言 在 Java EE 开发中,我们使用 Maven 构建工具主要来管理项目的第三方库的依赖,以及公司内部其他项目服务的依赖.因此 Maven 私服就是必不可少的一环,本文主要对 Maven 私服的 ...

  10. 80后,天才程序员, Facebook 第一任 CTO,看看开挂的人生到底有多变态?

    鸡仔说:今天介绍一位天才程序员--亚当·德安格洛(Adam D'Angelo),他被<财富>杂志誉为"科技界最聪明的人之一",大学去了被誉为"天才" ...