• ReentrantLock锁源码浅析


    定义

    公平锁:

    公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁

    公平锁的优点是等待锁的线程不会饿死。

    缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大

    公平锁

    非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待,但如果此时锁刚好可用,那么该线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景

    非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程

    缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁

    ReentrantLock是实现Lock接口的一种锁

    定义了一个final类型的Sync

    Sync使用AQS的state表示对锁的持有次数

    分为公平锁和非公平锁

     

    调用Lock方法

    lock方法,分为公平锁和非公平锁两个版本

    (1)非公平锁

     (2)CAS操作

    compareAndSetState(expect,update);

    如果当前状态值等于预期值,则原子地将同步状态设置为给定的更新值。 此操作具有易失性读写的内存语义。

    参数:期望 - 期望值,更新 - 新值

    返回:如果成功则为真。 假返回表示实际值不等于预期值。

    如果预期值位0那么设置为1

    最终也是调用sun.misc.Unsafe相关的方法,这个方法的四个参数第一个表示操作的是那个对象,第二个表示操作对象字段的偏移量,第三个是期望值,第四个是更新值

    (3)volatile

    state状态是用volatile来修饰的

    如果不使用volatile的时候,如果两个线程thread1和thread2同时执行同一个方法来修改state为1,当thread把state从0变为1时,thread2没有感知还以为state还是0,这样也成功把0修改为1,这样两个线程都认为自己成功执行了获取锁的行为

    volatile的功能:

    a: 保证变量在线程之间的可见性 就是上面说的

    b:禁止指令重排序 在编译阶段插入内存屏障,来特定禁止指令重排序

    如果使用volatile,两个线程thread1和thread2,当thread1写会成功之后会让其它线程中该变量的副本失效,把成功后的值刷回主内存,并重新从主存load新的,这样一来thread2 expect=0,update=1就会失败,因为此时的expect=0是不成立的

    JMM模型

    工作内存:

    每个线程都有自己的工作内存,里面保存了用到的变量和主内存的拷贝,叫做工作内存

    线程对变量进行操作都在这个拷贝中操作,而不能直接读写主内存中的变量,每个线程的工作内存都是独立的,线程操作数据只能在工作内存(虚拟机栈)中进行,然后刷回到主存(堆加方法区)。这是 Java 内存模型定义的线程基本工作方式

    当一个线程修改共享变量的值,其他线程能够立即知道被修改了,Java是利用volatile关键字来提供可见性的。当变量被volatile修饰时,这个变量被修改后会立刻刷新到主内存,当其它线程需要读取该变量时,会去主内存中读取新值。而普通变量则不能保证这一点

     

    非公平锁的流程

    因此在非公平锁中,AQS的volatile int state +1表示获取到了锁

    通过CAS设置AQS的成员status,大家注意到status是用volatile来修饰的,它在此处表示让所线程能够获取到最新更改的值

    设置当前拥有独占访问权限的线程。

    参数:Thread.curretThread()当前线程

    线程 - 所有者线程

    执行完lock方法后,调用tryAcquire方法

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

    获取当前线程,拿到当前锁状态

    如果没加锁就加锁,如果加了锁,把当前锁状态state+1

    若上述执行完成则代表成功,否则失败

    举例分析

    两个线程thread1和thread2进入方法,通过CAS方法进行争抢锁,谁能成功设把state从0设置为1,谁就能够把锁的独占线程设为自己。获取到锁的线程退出方法,进入业务逻辑。

    假设thread1获取到锁,在tryAcquire方法中,thread1如果又重入这把锁,那么会将state+1

    未获取到锁的thread2会通过else方法进入acquire方法中。然后进入tryAcquire方法,thread2未争抢到锁的线程进入acquire(1),因为thread1持有锁,那么本次tryAcquire返回false,进入addWaiter方法,这个方法是有一个FIFO的双向链表,进入链表后的线程是等待线程,waitStatus表示节点的状态,里面的结点入队之后可以自旋获取锁,自旋如果成功会将结点头从等待队列中摘除,thread2获取到锁,thread2执行业务逻辑。否则thread2一直会尝试获取锁,失败了返回false,直到成功了就返回true

     

    公平锁

    与非公平锁的区别是,非公平锁先通过CAS来抢占锁,然后在申请获得锁

    而公平锁直接申请获得锁

    以独占模式获取,忽略中断。 通过至少调用一次 tryAcquire 实现,成功返回。 否则线程会排队,可能会反复阻塞和解除阻塞,调用 tryAcquire 直到成功。 此方法可用于实现方法 Lock.lock

    它是ReentrantLock成员Sync的整个锁的逻辑

    tryAcquire方法是线程尝试以独占模式获取这个锁。如果允许则获取它。

    acquireQueued方法是以独占不间断模式获取已在队列中的线程,如果在等待时出现中断,则会返回true,没有中断则返回false

    如果线程无法独占模式获取锁,并且在等待时出现中断,那么中断当前线程

    公平锁

    首先获得当前线程current

    拿到状态

    公平锁先判断队列(双向链表)为空(head==tail)在进行cas抢占,最终两者为获取锁的线程都会进入到队列中

    因此,公平锁就是保障了多线程下各线程获取锁的顺序,先到的线程优先获得锁,后到的线程进入阻塞队列

  • 相关阅读:
    19年春第六周学习
    Java编程思想
    个人作业4-结对开发地铁
    Hadoop学习笔记—1.基本介绍与环境配置
    ZooKeeper学习第二期--ZooKeeper安装配置
    ZooKeeper学习第一期---Zookeeper简单介绍
    loadrunner--参数化—使用数据文件参数化
    loadrunner--analysis--图表筛选、合并、显示
    loadrunner--并发测试
    session和cookie
  • 原文地址:https://www.cnblogs.com/ak918xp/p/15359580.html
Copyright © 2020-2023  润新知