首页 > 其他分享 >synchronized 原理 (偏向锁、轻量级锁、锁膨胀、自旋)

synchronized 原理 (偏向锁、轻量级锁、锁膨胀、自旋)

时间:2024-02-27 11:00:45浏览次数:29  
标签:synchronized 对象 线程 轻量级 自旋 偏向

synchronized 原理

Synchronized 是 Java 中用于实现线程同步的关键字,它可以用于方法或代码块。当一个方法或代码块被 synchronized 修饰时,它将在任意时刻只允许一个线程访问,保证了多线程环境下的数据安全性。
synchronized可用于修饰对象或方法:

方法上的 synchronized

class Test{
     public synchronized void test() {
				// ...
     }
}
// 等价于
class Test{
     public void test() {
         synchronized(this) {
             // ...
         }
     }
}

class Test{
     public synchronized static void test() {
         // ...
     }
}
// 等价于
class Test{
     public static void test() {
         synchronized(Test.class) {
						// ...
         }
     }
}

对象上的 synchronized

synchronized(对象) // 线程1,线程2(blocked)
{
		临界区
}
static int counter = 0;
    static final Object room = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {
                    counter++;
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {
                    counter--;
                }
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",counter);
    }

synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。

偏向锁

一开始没有竞争时,加的是偏向锁。 Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有

对象头格式:
image

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的 thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 - XX:BiasedLockingStartupDelay=0 来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、 age 都为 0,第一次用到 hashcode 时才会赋值

撤销 - 调用对象 hashCode

调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销

  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode

在调用 hashCode 后使用偏向锁,记得去掉 -XX:-UseBiasedLocking

撤销 - 其它线程使用对象

当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

撤销 - 调用 wait/notify

偏向锁或者轻量级锁,在调用了 wait 方法之后均会直接膨胀为重量级锁。具体可以查看下面这篇对重量级锁底层源码的解析,其中有提到:

如果线程获得锁后调用Object#wait方法,则会将线程加入到WaitSet中,当被Object#notify唤醒后,会将线程从WaitSet移动到cxq或EntryList中去。需要注意的是,当调用一个锁对象的waitnotify方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。

批量重偏向

如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象 的 Thread ID

当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程

批量撤销

当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象 都会变为不可偏向的,新建的对象也是不可偏向的

参考资料 https://github.com/farmerjohngit/myblog/issues/12 https://www.cnblogs.com/LemonFive/p/11246086.html https://www.cnblogs.com/LemonFive/p/11248248.html 偏向锁论文

轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

轻量级锁对使用者是透明的,即语法仍然是 synchronized

假设有两个方法同步块,利用同一个对象加锁

  • 创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word
  • 让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存 入锁记录
  • 如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁
  • 如果 cas 失败,有两种情况
    • 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
    • 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数
  • 当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一
  • 当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头
    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

锁膨胀

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

  • 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁
  • 这时 Thread-1 加轻量级锁失败,进入锁膨胀流程
    • 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
    • 然后自己进入 Monitor EntryList BLOCKED
  • 当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁 流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程
    (对于对象的hashcode,轻量级锁会将存储在栈帧的锁记录里 重量级锁存储在monitor里)

自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。

  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。

  • Java 7 之后不能控制是否开启自旋功能

总结

锁的升级流程:无锁 --> 偏向锁 --> (有竞争时先进行一次CAS,失败后锁膨胀)重量级锁

标签:synchronized,对象,线程,轻量级,自旋,偏向
From: https://www.cnblogs.com/neko-yingying/p/18036444

相关文章

  • 动手实践丨轻量级目标检测与分割算法开发和部署(RK3568)
    本文分享自华为云社区《自动驾驶(AIOT)-轻量级目标检测与分割算法开发和部署(RK3568)【玩转华为云】》,作者:HouYanSong。本文将在ModelArts平台上开发轻量级目标检测与分割算法,并使用ModelBox框架在RK3568开发板上实现模型推理和部署。数据准备我们收集了一份200张由Labelme......
  • 高并发时为什么推荐ReentrantLock而不是synchronized
    目录1、最初的synchronized2、synchronized的优化3、但是,JAVA的最终答案JDK21LTS来了1、最初的synchronized它默认对临界资源添加重量级锁,即使可能并不存在竞争,只要走到临界区通通给你加锁。现在来回答问题:1)如果是低于JDK1.5,抱歉你没得选,只能先将就着用synchronize......
  • 多线程系列(三) -synchronized 关键字使用详解
    一、简介在之前的线程系列文章中,我们介绍了线程创建的几种方式以及常用的方法介绍。今天我们接着聊聊多线程线程安全的问题,以及解决办法。实际上,在多线程环境中,难免会出现多个线程对一个对象的实例变量进行同时访问和操作,如果编程处理不当,会产生脏读现象。二、线程安全问题介......
  • Spring Boot整合Postgres实现轻量级全文搜索
    有这样一个带有搜索功能的用户界面需求:搜索流程如下所示:这个需求涉及两个实体:“评分(Rating)、用户名(Username)”数据与User实体相关“创建日期(createdate)、观看次数(numberofviews)、标题(title)、正文(body)”与Story实体相关需要支持的功能对User实体中的评分(Rating)的频繁修......
  • synchronized和ReentrantLock有什么区别
    `synchronized`和`ReentrantLock`都是Java中用于实现同步的机制,但它们之间有一些区别:1.**可重入性**:  -`synchronized`是Java语言内置的关键字,具有可重入性,同一个线程可以多次获取同一个锁而不会造成死锁。  -`ReentrantLock`是`java.util.concurrent.locks`包下的类,也具......
  • 轻量级容器管理工具Containerd的两种安装方式
    1.yum安装1.1.获取阿里云YUM源[root@centos]#wget-O/etc/yum.repos.d/docker-ce.repohttps://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo1.2.查看YUM源中Containerd软件[root@centos]#yumlist|grepcontainerdcontainerd.io.x86_641.4.12-3.......
  • 阿里云轻量级 GPU 实例安装 NVIDIA 驱动
    实例规格:轻量级GPU实例vgn6i-vws/ecs.vgn6i-m4-vws.xlarge(4vCPU23GiB)操作系统:Ubuntu22.04第一部分:尝试失败的安装方法查询NVIDIA产品型号lspci|grep-invidia输出00:07.0VGAcompatiblecontroller:NVIDIACorporationTU104GL[TeslaT4](reva1)根据产......
  • 深入浅出Java多线程(九):synchronized与锁
    引言大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第九篇内容:synchronized与锁。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!在现代软件开发中,多线程技术是提升系统性能和并发能力的关键手段之一。Java作为主流的编程语言,其内置的多线程机制为开发者......
  • go-carbon v2.3.8 发布,轻量级、语义化、对开发者友好的 golang 时间处理库
    carbon是一个轻量级、语义化、对开发者友好的golang时间处理库,支持链式调用。目前已被awesome-go收录,如果您觉得不错,请给个star吧github.com/golang-module/carbongitee.com/golang-module/carbon安装使用Golang版本大于等于1.16//使用github库goget-ugithu......
  • Stable Code 3B:轻量级编程助手,无GPU本地运行
    引言StabilityAI近期发布了StableCode3B,这是一个集中了多项创新技术的轻量级编程辅助模型。它在保持轻量的同时,展现出了与大型模型如CodeLLaMA7B相媲美的性能,这一特性使其在没有GPU的环境中也能运行,极大地拓宽了其应用范围。模型概述StableCode3B,作为一款拥有30亿参数的编程......