Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >jvm源码解析(五)synchronized和ReentrantLock

jvm源码解析(五)synchronized和ReentrantLock

作者头像
JathonKatu
发布于 2020-10-27 06:39:37
发布于 2020-10-27 06:39:37
43600
代码可运行
举报
文章被收录于专栏:JathonKatuJathonKatu
运行总次数:0
代码可运行

一、Synchronized和ReentrantLock是怎么实现的,他们有什么区别

synchronized属于独占式悲观锁,通过jvm隐式实现,只允许同一时刻只有一个线程操作资源。

java中,每个对象都隐式包含一个monitor(监视器)对象

加锁的过程其实就是竞争monitor的过程

当线程进入字节码monitorenter指令之后

线程将持有monitor对象,执行monitorexit时释放monitor对象

当其他线程没有拿到monitor对象时,则需阻塞等待,获取该对象

ReentrantLock是Lock的默认实现方式之一

是基于AQS(Abstract Queued Synchronizer,队列同步器)实现的,默认是通过非公平锁实现的

内部有一个state的状态字段,用于表示锁是否被占用

如果是0则表示锁未被占用,此时线程就可以把state改成1,并成功获得锁

而其他未获得锁的线程只能排队等待获取锁的资源

区别如下:

synchronized是jvm隐式实现的,而ReentrantLock是Java语言提供的API

ReentrantLock可设置成公平锁,而synchronized不行

ReentrantLock只能修饰代码块,而synchronized可以修饰方法,代码块等

ReentrantLock需要手动加锁和释放锁,如果忘了释放就会造成资源永久使用

synchronized则无需手动释放锁

ReentrantLock可以知道是否获得了锁,而synchronized不行

两者都提供了锁的功能,具备互斥性和不可见性,在jdk5中,synchronized的性能远远低于ReentrantLock,但在jdk6之后synchronized的性能只是略低于ReentrantLock

MarkWord的字节码:

公平锁与非公平锁:

线程需要按照请求的顺序来获得锁,

非公平锁则允许“插队”的情况存在

插队:线程在发送请求的同时,该锁的状态恰好变成了可用,那么此线程就可以跳过队列中所有排队的线程,直接拥有锁。

频繁的挂起和恢复会造成一定的开销,所以公平锁的性能不如非公平锁,所以ReentrantLock和Synchronized默认都是非公平锁来实现的。

二、大厂高频面试题

ReentrantLock的实现细节是什么

先解释waitStatus的值有哪些,后面用得到

(源码里的注释太长,就不放了)

  • CANCELLED =1 线程被取消了(超时或者interrupt引起)
  • SIGNAL =-1 释放资源后需唤醒后继节点(如果节点为这个状态,后续节点是挂起状态)
  • CONDITION = -2 等待condition唤醒(位于condition队列,不在同步队列中,需要从condition队列中拿出来)
  • PROPAGATE = -3 (共享锁)状态需要向后传播
  • 0 初始状态,正常状态(默认)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock是通过lock来加锁,通过unlock来释放锁。

1、lock:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void lock() {
    sync.acquire(1); // 调用AbstractQueuedSynchronizer.acquire
}

1.1、AbstractQueuedSynchronizer.acquire:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public final void acquire(int arg) {
    // 调用ReentrantLock.NonfairSync或FairSync.tryAcquire
    if (!tryAcquire(arg) &&
        // 独占锁中,获取锁失败:// 将当前线程包装进Node
            // 若当前线程是head节点的后置节点,且head状态为cancelled
            // 则尝试获取锁,若成功,则将head节点设置为null,帮助原来的head节点gc(此时等待队列没有Node了)
            // 否则尝试挂起当前线程
                // 如果前驱节点的状态为Signal,则挂起当前线程
                // 如果前驱节点被cancelled,则尝试找到非cancelled状态的节点,并将他的next设置成当前节点
                // 否则(前驱节点的waitStatus<0),则将前驱节点的状态设置成0
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 调用Thread.currentThread().interrupt();停止线程
        selfInterrupt(); 
}

NonfairSync.tryAcquire调用的是Sync的nonfairTryAcquire,所以我们直接分析nonfairTryAcquire和Fair.tryAcquire,公平锁比非公平锁多了一行!hasQueuedPredecessors() ,用来查看等待队列是否有已经在排队的线程

1.1.1、Sync.nonfairTryAcquire

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 保护被注解的方法,通过添加一些额外的空间,防止在多线程运行的时候出现栈溢出
@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取当前锁状态
    int c = getState();
    // 没有线程持有锁
    if (c == 0) {
    // cas尝试将锁状态换成1
        if (compareAndSetState(0, acquires)) {
         // 如果cas成功,则将当前线程设置成当前锁的持有线程
            setExclusiveOwnerThread(current);
            return true; // 返回上锁成功
        }
    }
    // 如果有线程持有锁,且持有线程是当前线程
    else if (current == getExclusiveOwnerThread()) {
        // 锁状态 + 1 可重入锁--可重入的含义
        int nextc = c + acquires;
        // 重入次数溢出,超过Integer.MAX_VALUE导致变成负数
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 设置锁状态
        setState(nextc);
        // 返回获取锁成功
        return true;
    }
    // 已经有线程获取锁,且非当前线程,返回获取锁失败
    return false;
}

1.1.2、Fair.tryAcquire

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取当前锁状态
    int c = getState();
    // 没有线程持有锁
    if (c == 0) {
        // 等待队列没有待唤醒且仍存活的线程
        if (!hasQueuedPredecessors() &&
       // 尝试获取当前锁 
            compareAndSetState(0, acquires)) {
            // 获取成功,将当前线程设置为当前锁所有线程
            setExclusiveOwnerThread(current);
            // 返回获取锁成功
            return true;
        }
    }
    // 如果有线程持有锁,且持有线程是当前线程
    else if (current == getExclusiveOwnerThread()) {
        // 锁状态 + 1
        int nextc = c + acquires;
        // 重入次数溢出,超过Integer.MAX_VALUE导致变成负数
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 设置锁状态
        setState(nextc);
        // 返回获取成功
        return true;
    }
    // 返回获取失败
    return false;
}

2、unlock:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void unlock() {
    sync.release(1); // 调用AbstractQueuedSynchronizer.release
}

2.1、AbstractQueuedSynchronizer.release:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public final boolean release(int arg) {
    // 调用ReentrantLock.Sync.tryRelease(1)
    if (tryRelease(arg)) {
        // 获取等待队列的head
        Node h = head;
        // 如果等待队列有node,且线程并不是处于初始状态
        if (h != null && h.waitStatus != 0)
            //调用AbstractQueuedSynchronizer.unparkSuccessor,尝试唤醒等待队列头部线程
            unparkSuccessor(h);
        // 返回解锁成功
        return true;
    }
    // 返回失败
    return false;
}

2.2、ReentrantLock.Sync.tryRelease

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
    // 持有锁状态 - 1(放一次锁,但不一定保证是把锁直接放掉,有可能重入了)
    int c = getState() - releases;
    // 如果当前线程不是锁持有者,则报错
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 设置返回的状态,标识着锁是否完全释放
    boolean free = false;
    // 锁状态为0(完全不持有)
    if (c == 0) {
        // 返回值设置为已释放锁,且锁持有线程设置为null
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 设置锁状态
    setState(c);
    // 返回锁是否已经没有线程持有
    return free;
}

2.3、AbstractQueuedSynchronizer.unparkSuccessor

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void unparkSuccessor(Node node) {
    // 获取等待状态
    int ws = node.waitStatus;
    // 等待状态小于0,则设置为初始状态
    if (ws < 0)
        node.compareAndSetWaitStatus(ws, 0);
    // 获取下一个node(队列中下一个线程)
    Node s = node.next;
    // node为空或者状态为cancelled
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 循环找到下一条能执行的线程
        for (Node p = tail; p != node && p != null; p = p.prev)
            if (p.waitStatus <= 0)
                s = p;
    }
    // 如果有下一个能执行的线程
    if (s != null)
        // 给node增加调用凭证
        // 调用线程的时候会判断,如果凭证不为0则挂起
        // 凭证只能有1个,所以unpark多次也是一样的效果
        LockSupport.unpark(s.thread);
}

JDK1.6时锁做了哪些优化

自适应式自旋锁,锁升级

JDK1.6引入自适应自旋锁,意味着自旋时间不再固定

比如在同一个锁对象上,如果通过自旋等待成功获取了锁,那么虚拟机就会认为,它下一次很有可能也会成功(通过自旋获取到锁),因此自旋等待的时间会比较长,相反,则比较短,甚至直接忽略自旋,避免浪费cpu资源。

锁升级就是从偏向锁,到轻量级锁,再到重量级锁的升级过程。是JDK1.6提供的优化功能,也称为锁膨胀。

偏向锁是指在无竞争的情况下设置的一种锁状态,意思是他会偏向第一个获取它的线程,当锁对象第一次被获取到之后,会在此对象头中设置01表示偏向锁模式,并且在对象头中记录此线程ID。偏向锁可以提高带有同步,但无竞争的程序性能。但如果在多数锁总会被不同线程访问时,偏向锁模式就比较多余。可以通过-XX:-UseBiasedLocking来禁用偏向锁以提高性能。

轻量锁是相对重量锁而言的。

在JDK1.6之前,Synchronized是通过操作系统的互斥量(mutex Lock)实现的,这种实现方式需要在用户态和核心态之间做转换,有很大的性能消耗,这种传统实现锁的方式被称为重量锁

轻量锁是通过比较并交换(CAS,Compare and Swap)来实现的,它对比的是线程和对象的Mark Word,如果更新成功,则表示当前线程成功拥有此锁,如果失败,虚拟机会先检查对象的MarkWord是否指向当前线程的栈帧。如果是,则说明当前线程已经拥有了此锁,否则,则说明此锁已经被其他线程占用,当有两个以上的线程竞争锁时,锁就会膨胀,升级成重量锁。

有两个锁写的不错的相关博客,可以参考

https://www.cnblogs.com/deveypf/p/11406932.html

https://www.jianshu.com/p/73b9a8466b9c

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JathonKatu 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
带你学习 ReentrantLock
一个线程可多次获取锁,但同时也要释放相同的次数,否则该线程将持续拥有锁,其他线程将无法进入临界区。
大发明家
2021/12/18
3330
高并发编程-ReentrantLock非公平锁深入解析
ReentrantLock是一个可重入的互斥锁,它不但具有synchronized实现的同步方法和同步代码块的基本行为和语义,而且具备很强的扩展性。ReentrantLock提供了公平锁和非公平锁两种实现,在默认情况下构造的ReentrantLock实例是非公平锁,可以在创建ReentrantLock实例的时候通过指定公平策略参数来指定是使用公平锁还是非公平锁。本篇将基于JDK7深入源码解析非公平锁的实现原理。
JavaQ
2018/10/23
5030
ReentrantLock与AQS
AbstractQueuedSynchronizer(以下简写AQS)这个抽象类,因为它是 Java 并发包的基础工具类,是实现 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等类的基础。
leobhao
2022/06/28
1880
ReentrantLock与AQS
面试官:从源码角度讲讲ReentrantLock及队列同步器(AQS)
JDK 中独占锁(排他锁)的实现除了使用关键字 synchronized 外,还可以使用ReentrantLock。虽然在性能上 ReentrantLock 和 synchronized 没有什么大区别,但 ReentrantLock 相比 synchronized 而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。
业余草
2021/12/06
3710
面试官:从源码角度讲讲ReentrantLock及队列同步器(AQS)
ReentranLock及源码解析(学思想,一步一步点进源码)
我们知道在并发的场景下,如果同时对共享代码块进行访问时,会导致原子性、有序性、可见性问题。从而导致我业务出错。所以有时候,我们有些代码块不能进行并行,就得去改成串行!!之前我们已经知道了一个Synchronized重量级锁。该锁底层是JVM里面,通过monitor锁来实现串行,其他线程进行等待。
向着百万年薪努力的小赵
2022/12/02
2550
ReentranLock及源码解析(学思想,一步一步点进源码)
AQS源码分析之ReentrantLock
在该方法内部会调用非公平锁java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire和 acquireQueued和addWaiter方法,这些方法会在后面进行分析。
山行AI
2020/03/25
3780
快速掌握并发编程---细说ReentrantLock和AQS
面试时常会被问synchronized和ReentrantLock的区别,我们前面文章中已经讲过synchronized同步锁关键字的相关知识点,今天就来聊聊ReentrantLock。ReentrantLock是java.util.concurrent(简称JUC)下使用频率相当高的工具类。
田维常
2020/11/03
4240
快速掌握并发编程---细说ReentrantLock和AQS
ReentrantLock 与 AQS 源码分析
ReentrantLock 与 AQS 源码分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读起来没有 IDE 方便,所以在 github 上提供JDK1.8 的源码、详细的注释及测试用例。欢迎大家 star、fork ! 2. 由于个人水平有限,对源码的分析理解可能存在偏差或不透彻的地方还请大家在评论区指出,谢谢! 1. 基本结构    重入锁 ReetrantLock,JDK 1.5新增的类,作用与synchronized关键字相当,但比sy
lwen
2018/04/11
8460
ReentrantLock 与 AQS 源码分析
Synchronized 和 Lock 锁在JVM中的实现原理以及代码解析
synrhronized关键字简洁、清晰、语义明确,因此即使有了Lock接口,使用的还是非常广泛。其应用层的语义是可以把任何一个非null对象作为"锁",当synchronized作用在方法上时,锁住的便是对象实例(this);当作用在静态方法时锁住的便是对象对应的Class实例,因为Class数据存在于永久带,因此静态方法锁相当于该类的一个全局锁;当synchronized作用于某一个对象实例时,锁住的便是对应的代码块。在HotSpot JVM实现中,锁有个专门的名字:对象监视器。
小勇DW3
2018/08/30
2.1K4
Synchronized 和 Lock 锁在JVM中的实现原理以及代码解析
Java - ReentrantLock实现细节
话不多说,下面通过流程图及源码介绍ReentrantLock的实现细节。 先看下逻辑流程图,总体统揽:
夹胡碰
2020/12/25
3630
ReentrantLock
ReentrantLock是 java提供代码层面的锁,和synchronized关键字相同。为什么在用提供了 synchronized关键字后,还提供了ReentrantLock等代码层面的锁API,首先在synchronized关键字刚推出是,性能方面差很多,直到后面的版本中推出了锁升级的概念,在性能上有所好转。更重要的也是,JUC的包里面,提供的API更加灵活,符合生产环境各种需求。
虞大大
2020/08/26
6890
ReentrantLock
ReentrantLock是如何基于AQS实现的
ReentrantLock是一个可重入的互斥锁,基于AQS实现,它具有与使用 synchronized 方法和语句相同的一些基本行为和语义,但功能更强大。
本人秃顶程序员
2019/04/24
4880
ReentrantLock是如何基于AQS实现的
从ReentrantLock的实现看AQS的原理及应用
AQS作为JUC中构建锁或者其他同步组件的基础框架,应用范围十分广泛,这篇文章会带着大家从可重入锁一点点揭开AQS的神秘面纱。
美团技术团队
2019/12/10
1.7K2
从ReentrantLock的实现看AQS的原理及应用
ReentrantLock源码解析
谈到多线程,就不避开锁(Lock),jdk中已经为我们提供了好几种锁的实现,已经足以满足我们大部分的需求了,今天我们就来看下最常用的ReentrantLock的实现。 其实最开始是想写一篇关于StampedLock的源码分析的,但发现写StampedLock前避不开ReentrantReadWriteLock,写ReentrantReadWriteLock又避不开ReentrantLock,他们仨是逐层递进的关系。ReentrantReadWriteLock解决了一些ReentrantLock无法解决的问题,StampedLock又弥补了ReentrantReadWriteLock的一些不足,三者有各自的设计和有缺点,这篇文章先和你一起看下ReentrantLock,之后我们会再一起去了解ReentrantReadWriteLock和StampedLock,相信有了ReentrantLock的基础后面的内容也会容易理解很多。
xindoo
2021/01/22
3600
ReentrantLock源码解析
不能再被问住了!ReentrantLock 源码、画图一起看一看!
" 在阅读完 JUC 包下的 AQS 源码之后,其中有很多疑问,最大的疑问就是 state 究竟是什么含义?并且 AQS 主要定义了队列的出入,但是获取资源、释放资源都是交给子类实现的,那子类是怎么实现的呢?下面开始了解 ReentrantLock。 "
程序员小航
2020/11/23
3420
不能再被问住了!ReentrantLock 源码、画图一起看一看!
ReentrantLock
CAS是一种无锁算法。有3个操作数:内存值V、旧的预期值A、要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
hhss
2021/02/12
4550
ReentrantLock
并发编程之深入理解ReentrantLock和AQS原理
AQS(AbstractQueuedSynchronizer)在并发编程中占有很重要的地位,可能很多人在平时的开发中并没有看到过它的身影,但是当我们有看过concurrent包一些JDK并发编程的源码的时候,就会发现很多地方都使用了AQS,今天我们一起来学习一下AQS的原理,本文会用通俗易懂的语言描述AQS的原理。当然如果你了解CAS操作、队列、那么我相信你学习起来会感到无比轻松。
全栈程序员站长
2021/08/05
3080
ReentrantLock 源码浅析
ReentrantLock 介绍 一个可重入的互斥锁,它具有与使用{synchronized}方法和语句访问的隐式监视器锁相同的基本行为和语义,但它具有可扩展的能力。 一个ReentrantLock会被最后一次成功锁定(lock)的线程拥有,在还没解锁(unlock)之前。当锁没有被其他线程拥有的话,一个线程执行『lock』方法将会返回,获取锁成功。一个方法将会立即的返回,如果当前线程已经拥有了这个锁。可以使用『isHeldByCurrentThread』和『getHoldCount』来检查当前线程是否持有
tomas家的小拨浪鼓
2018/06/27
1.8K1
从源码来看ReentrantLock和ReentrantReadWriteLock ReentrantLockReentrantReadWriteLock
上一篇花了点时间将同步器看了一下,心中对锁的概念更加明确了一点,知道我们所使用到的锁是怎么样获取同步状态的,我们也写了一个自定义同步组件Mutex,讲到了它其实就是一个简版的ReentrantLock,本篇文章我们就来看看ReentrantLock底层是怎么样的! 目录结构: ReentrantLock 公平锁与非公平锁 ReentrantReadWriteLock ---- ReentrantLock ReentrantLock我们叫做可重入锁,也就是我们可以重复进入的意思,也就是表示,一个线程可以对指定
MindMrWang
2018/04/16
7710
从源码来看ReentrantLock和ReentrantReadWriteLock	ReentrantLockReentrantReadWriteLock
死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁
Reentrant = Re + entrant,Re是重复、又、再的意思,entrant是enter的名词或者形容词形式,翻译为进入者或者可进入的,所以Reentrant翻译为可重复进入的、可再次进入的,因此ReentrantLock翻译为重入锁或者再入锁。
彤哥
2019/07/08
5330
死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁
相关推荐
带你学习 ReentrantLock
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验