主要分析下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. charles(version4.2.1)抓包手机数据

    点击菜单栏的Proxy项,选择Proxy Settings. 设置HTTP Proxy的Port. 勾选透明代理Enable transparent HTTP proxying,也可不勾选. 设置代理 ...

  2. Linux故障处理最佳实践

    引言 业务中断了! 老板咆哮,主管抓狂,而你就是那个要去处理故障.恢复业务的不幸的人. 你独自一人在阴暗的隔间里.北边是老板的办公室,西边是Team Leader的办公室,南面是茶水间,在那你能泡上一 ...

  3. 守望先锋app(1)

    这个app就是从守望先锋的官网下载相关的图片.文字.视频然后展示出来. 第一个功能是英雄介绍,所以先分析一波官网的数据.守望先锋的英雄数据的官方网站是http://ow.blizzard.cn/her ...

  4. web面试

    什么是面向对象? 程序中的事物都是用对象结构来描述的,所有的程序都是用面向对象的思想,管理数据和功能的. 面向对象三大特点:封装 继承 多态 什么是封装? 创建一个对象结构,用来保存一个事物的属性和方 ...

  5. 洛谷 P1120 小木棍

    题意简述 给出n个数,求最小的l,使n个数可分成若干组,每组和都为l. 题解思路 暴力搜索+剪枝 代码 #include <cstdio> #include <cstdlib> ...

  6. ZooKeeper实现同步屏障(Barrier)

    按照维基百科的解释:同步屏障(Barrier)是并行计算中的一种同步方法.对于一群进程或线程,程序中的一个同步屏障意味着任何线程/进程执行到此后必须等待,直到所有线程/进程都到达此点才可继续执行下文. ...

  7. mybatis批量更新策略

    我们知道循环中操作db会导致连接数满,严重影响数据库性能.所以在对db进行DQL与DML时,根据业务逻辑尽量批量操作,这里我们介绍下使用mybatis批量更新mysql的两种方式. 方式一: < ...

  8. netty源码解解析(4.0)-18 ChannelHandler: codec--编解码框架

    编解码框架和一些常用的实现位于io.netty.handler.codec包中. 编解码框架包含两部分:Byte流和特定类型数据之间的编解码,也叫序列化和反序列化.不类型数据之间的转换. 下图是编解码 ...

  9. PostgreSQL创建扩展出错

    问题: loraserver_as=# create extension pg_trgm; ERROR: could not open extension control file "/us ...

  10. net start mysql

    net start mysql 解决的方法: 如何以管理员身份打开黑窗口 左下角开始菜单,找到小娜,cmd 回车, 命令提示符右击,以管理员身份打开 依次输入下面两行代码 mysqld -instal ...