JUC-synchronized无锁、偏向锁、轻量级锁、重量级锁

1 synchronized实操

关键字synchronized可以用来保证多线程并发安全的**原子性、可见、有序性。**关键字synchronized不仅可以作用于方法还可以作用于同步代码块,能够保证作用范围中线程访问安全。

注意:局部变量是线程安全的。线程不安全问题只存在于实例变量。

synchronized关键字作用如下图:
在这里插入图片描述

  • 同步方法:分为静态方法和非静态方法,对静态方法,锁作用对象是类对象。对非静态方法,锁作用对象是实例对象。
  • 同步方法块:通过synchronized(obj)来对某个对象进行加锁。如果是this表示实例对象。如果是xx.class表示作用于类对象

1.1 锁作用于类对象和实例对象的区别?

  • 锁作用于实例对象:当多个线程访问同一个实例对象的同步块的时候,存在竞争关系。只能有一个线程能访问当前实例对象的锁。其他线程只能等待,直到占所有的线程释放实例对象的锁。
  • 锁作用于类对象:当多个线程访问同一个类的不同实例对象的同步块的时候,存在竞争关系。因为锁所用于类上,虽然是不同的实例对象但是所属同一个类,只有一把类锁。因此多个线程仍然存在竞争关系。

举例

public class SynchronizedTest {
    public synchronized static void test1() {
    // 类锁
    }
    public synchronized void test2() {
    // 实例对象锁
    }
    public void test3() {
        synchronized (SynchronizedTest.class) {
     	// 类锁
        }
    }
    public void test4() {
        synchronized (this) {
        // 实例对象锁
        }
    }
    public void test5() {
        synchronized ("a") {
        // 实例对象锁
        }
    }
}

非静态同步方法用的是同一把锁:实例对象本身
静态同步方法用的是同一把锁:类本身
注意:无加锁的方法块和加锁的方法块不存在竞争关系。静态同步块和非静态同步块也不存在竞争关系,因为持有的是不同的锁。

2 synchronized原理

2.1 synchronized对应字节码指令

2.1.1 同步方法

使用命令:

javap -c -v synchronizedTest.class

注:-c是查看字节码指令 -v是打印附加信息
在这里插入图片描述
在同步方法中,加了synchronized关键字,方法的flag标志有ACC_SYNCHRONIZED标志

2.1.2 同步代码块

同样的使用上述命令查看字节码
在这里插入图片描述
可以看到当用synchronized()表示同步代码块后,字节码对应会有monitorentermonitorexit指令,表示获取锁,释放锁。

3 synchronized锁升级

jdk1.6之前synchronized的实现都为重量锁,重量锁需要用户态切入到内核态获取锁对象,影响性能。
jdk1.6及其之后进行了优化,引入了偏向锁和轻量级锁(性能比重量锁更好),以及锁存储结构和锁升级,也就是说synchronized同步块会根据具体情况从无锁->偏向锁->轻量级锁->重量级锁 升级锁。

3.1 无锁

线程之间不会有锁的竞争。多线程下会造成数据的不安全。

3.2 偏向锁

顾名思义,偏向于某个线程的锁。会偏向于第一个访问锁的线程

原理:
当线程进入同步块的时候,获取到锁之后,退出同步块的时候不会主动释放锁。当线程第二次进入同步代码块的时候,会判断此时持有锁的线程是不是自己(持有锁对象的线程存放在对象头中)。如果是自己正常执行。从始至终能使用锁的只有一个线程,性能很高。(java 15被废弃)
简言之,线程进入的时候获取锁,不会释放。下次线程来的时候直接用。
偏向锁只有在其他线程尝试获取偏向锁的时候才会释放。竞争的线程用CAS来替换对象头中的持有锁线程ID。

  • 如果竞争成功,仍然为偏向锁。只不过偏向新的线程。
  • 如果竞争失败,升级为轻量级锁。

3.3 轻量级锁

多个线程下,基本不会出现或者轻微的锁的竞争,那么synchronized就处于轻量级锁的状态下。这种状态允许线程出现短时间的CAS自旋空转。
没有抢到的锁会自旋,即不停地循环判断是否能够获取锁。获取锁的操作其实是通过CAS修改对象头中的锁标志位。
注:轻量级锁和偏向锁有一个重要的区别是偏向锁获得锁之后不会主动释放。轻量级锁获得锁之后会主动释放。

3.4 重量级锁

线程竞争锁过程中长时间的自旋是非常影响性能的。因此会用计数器记录自旋次数(默认是10次)如果超出限定次数。会将轻量级锁升级为重量级锁。
进入重量级锁状态,当一个线程获取锁之后。其余线程会进入阻塞状态。简言之,就是所有的控制权交给了操作系统,操作系统来负责线程的切换,会频繁的从用户态到内核态的切换,严重影响性能。

4 synchronized锁升级总结

优点缺点适用场景
偏向锁加锁和解锁都不需要额外的消耗如果多个线程竞争会带来额外的锁撤销消耗只有一个线程访问同步块
轻量级锁竞争的线程不会阻塞,提高程序的相应速度线程竞争一直得不到锁,会自旋消耗cpu性能追求响应时间,同步块执行速度特别快
重量级锁竞争的线程会自旋,不会消耗CPU线程阻塞,用户态和内核态切换时间长,响应时间慢追求吞吐量,同步执行时间长

简单描述锁升级过程:
单个线程竞争适合偏向锁。两个线程竞争锁的时候,竞争成功,会偏向另一个线程。竞争失败,升级为轻量级锁。轻量级锁适合多个线程少量竞争,CAS自旋次数有一定限制的锁争抢。当CAS自旋次数超过限度,会升级为重量级锁。

5 思考问题

  1. 为什么会存在锁升级现象?
    JDK1.6之前sychronized只有重量级锁。重量级锁需要依靠操作系统来完成线程的切换。线程切换需要从用户态到内核态,他们分别有自己专用的内存空间,导致用户态到内核态切换很耗时,影响程序性能。
    为了减少线程获取锁带来的性能消耗,引入了偏向锁和轻量级锁。
  2. 为什么每个对象都可以成为一个锁?
    因为每个对象的对象头里面都有一个锁标志,表示这个对象的锁的状态。深入底层理解的话,因为每个对象实例都有一个Monitor,Monitor和实例对象一起创建一起销毁。实例对象中头中有标志关联Monitor,而Monitor中owner属性存放持有锁的线程id。
  3. 锁消除是什么?
    锁消除是JIT即时编译器的优化手段。属于代码bug,代码虽然加锁了,但是每个线程都不会出现竞争的情况(每把锁都不相同)。比如下面代码:
public class SynchronizedTest2 {
    public static void main(String[] args) {
        Cat cat = new Cat();
        for (int i=0;i<5;i++){
            new Thread(()->{
                cat.test();
            }).start();
        }

    }
    private static class Cat{
        static Object object = new Object();
        public void test(){
        // 每个线程进来都新new一个对象,所以每个线程的锁都不一样,相当于无锁
            Object o = new Object();
            synchronized (o){
System.out.println(object.hashCode()+"===="+o.hashCode());
            }
        }
    }
}

  1. 锁粗化是什么?
    也是JIT即时编译器优化手段。将相邻的锁住同一个锁对象的代码合并。比如
public class LockBigDemo
{
    static Object objectLock = new Object();

    public static void main(String[] args)
    {
        new Thread(() -> {
            synchronized (objectLock){
                System.out.println("111111");
            }
            synchronized (objectLock){
                System.out.println("222222");
            }
            synchronized (objectLock){
                System.out.println("333333");
            }
            synchronized (objectLock){
                System.out.println("444444");
            }
            // JTI进行锁粗化后的代码相当于:
            synchronized (objectLock){
                System.out.println("111111");
                System.out.println("222222");
                System.out.println("333333");
                System.out.println("444444");
            }

        },"t1").start();
    }
}

6 参考链接

  1. 锁升级:无锁、偏向锁、轻量级锁、重量级锁
  2. synchronized无锁、偏向锁、轻量级锁、重量级锁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/352122.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

excel中多行合并后调整行高并打印

首先参考该文&#xff0c;调整全文的行高。 几个小技巧&#xff1a; 1.转换成pdf查看文件格式 2.通过视图--》分页预览&#xff0c;来确定每页的内容&#xff08;此时页码会以水印的形式显示&#xff09; 3. 页面布局中的&#xff0c;宽度可以选为自动&#xff0c;因为已经是…

ASP.NET 7 Core Web 读取appsetting.json

把一些配置信息保存在json文件可以避免更改时要重新发布程序的烦恼。 我这里使用的是写一个类文件&#xff0c;然后通过program.cs启动的方式&#xff08;.net 6 开始没有startup了&#xff09;。 项目类型&#xff1a;ASP.NET Core Web MVC / .NET 7.0 / VS2022 第一步…

苹果提审被拒反馈崩溃日志.text | iOS 审核被拒crashLog

iOS审核人员拒绝后每个截图&#xff0c;只给了几个text文件&#xff0c;这种情况就是审核的时候运行你的代码&#xff0c;崩溃了。 仅仅看text文件&#xff0c;是看不出所以然来的&#xff0c;所以我们要将日志转换成.crash格式 1.将.text文件下载下来&#xff0c;将 .text手动…

OpenHarmony关系型数据库

1 概述 关系型数据库(Relational Database, 以下简称RDB)是一种基于关系模型来管理数据的数据库&#xff0c;是在SQLite基础上提供一套完整的对本地数据库进行管理的机制&#xff0c;为开发者提供无需编写原生SQL语句即可实现数据增、删、改、查等接口&#xff0c;同时开发者也…

算法笔记:地理探测器

1 空间分层异质性&#xff08;spatial stratified heterogeneity&#xff09; 空间分层异质性&#xff08;空间分异性/区异性&#xff09;&#xff1a;层内方差小于层间方差的地理现象例如气 候带、土地利用图、地貌图、生物区系、区际经济差异、城乡差异以及主体功能区等 等[…

张维迎《博弈与社会》笔记(3)导论:一些经济学的基础知识

这篇的主要内容介绍了经济学的基础知识吧。 经济学、社会学、心理学的区别 经济学与社会学的区别与共同点 经济学一般是从个人的行为出发解释社会现象&#xff08;from micro to macro&#xff09;。社会学的传统方法则是从社会的角度来解释个人的行为&#xff08;from macro…

Oracle分栏(非分页)查询

不知道Oracle怎么进行数据分栏(分栏: 因数据列过长, 部分数据作为新列显示). 在这里先记录一下粗浅的查询方法. 数据源例子: select 日用百货 as cat, 手电筒 as name, 20 as amount, 2024-01-27 as dt from dualunion allselect 餐饮美食 as cat, 鸡公煲 as name, 15.9 as amo…

外卖跑腿系统开发:构建高效、安全的服务平台

在当今快节奏的生活中&#xff0c;外卖跑腿系统的开发已成为技术领域的一个重要课题。本文将介绍如何使用一些常见的编程语言和技术框架&#xff0c;构建一个高效、安全的外卖跑腿系统。 1. 技术选择 在开始开发之前&#xff0c;我们需要选择适合的技术栈。常用的技术包括&a…

Java 字符串 10 字符串相关类的底层原理

底层原理1&#xff0c;底层原理2 底层原理3&#xff1a; 分两种情况&#xff1a; 1、等号右边没有变量&#xff1a; 2、等号右边有变量&#xff1a; 两个对象&#xff0c;一个是StringBuilder&#xff0c;一个是String&#xff0c;浪费空间&#xff0c;性能不高 在jdk8之前&am…

设计模式⑩ :用类来实现

文章目录 一、前言二、Command 模式1. 介绍2.应用3. 总结 三、Interpreter 模式1. 介绍2. 应用3. 总结 参考文章 一、前言 有时候不想动脑子&#xff0c;就懒得看源码又不像浪费时间所以会看看书&#xff0c;但是又记不住&#xff0c;所以决定开始写"抄书"系列。本系…

go语言(十九)---- channel

channel的使用 //1. 发送value到channelchannel <- value //2. 接收并将其丢弃<- channel //3. 从channel中接收数据&#xff0c;并将其赋值给x x : <- channel 例子 package mainimport "fmt"func main() {//定义一个channelc : make(chan int)go func…

Qlik Sense : ErrorCode(错误变量)

错误变量 所有错误变量的值在脚本执行之后依然保留。第一个变量 ErrorMode 由用户输入&#xff0c;最后三个变量是 Qlik Sense 的输出&#xff08;包括脚本中错误的信息&#xff09;。 使用每个变量的下拉列表可查看每个变量的简短描述和语法。单击语法描述中的变量名称可了解…

Java强训day6(选择题编程题)

选择题 class HelloA{public HelloA(){System.out.println("I’m A class ");}static{System.out.println("static A");} } public class Test01 extends HelloA{public Test01(){System.out.println("I’m B class");}static{System.out.print…

用C语言实现贪吃蛇游戏!!!(破万字)

前言 大家好呀&#xff0c;我是Humble&#xff0c;不知不觉在CSND分享自己学过的C语言知识已经有三个多月了&#xff0c;从开始的C语言常见语法概念说到C语言的数据结构今天用C语言实现贪吃蛇已经有30余篇博客的内容&#xff0c;也希望这些内容可以帮助到各位正在阅读的小伙伴…

【Vue】1-1、webpack的基本使用

一、什么是 Webpack 概念&#xff1a; webpack 是前端项目工程化的具体解决方案。 主要功能&#xff1a; 它提供了友好的前端模块化开发支持&#xff0c;以及代码压缩混淆、处理浏览器端 JavaScript 的兼容性、性能化等强大的功能。 好处&#xff1a; 让程序员把工作重心放到具…

JVM系列-7内存调优

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术、JVM原理&#x1f525;如果感觉博主的文…

课时5:编程语言解读

1.2.1 编程语言解读 学习目标 这一节&#xff0c;我们从 基础知识、编程语言、小结 三个方面来学习。 基础知识 程序 外在关系&#xff1a;业务数据&#xff1a;用户访问业务时候&#xff0c;产生的信息内容数据结构&#xff1a;静态的描述了数据元素之间的关系算法&#x…

PHP伪协议使用姿势

php支持的伪协议 1 file:// — 访问本地文件系统 2 http:// — 访问 HTTP(s) 网址 3 ftp:// — 访问 FTP(s) URLs 4 php:// — 访问各个输入/输出流&#xff08;I/O streams&#xff09; 5 zlib:// — 压缩流 6 data:// — 数据&#xff08;RFC 2397&#xff09; 7 glob:// —…

rqt查看rosbag中视频的方法

1. 播放bag视频 执行&#xff1a; rosbag play xxx.bag2. 打开rqt_image_view 执行&#xff1a; rqt_image_view3. 在选择话题处选择图片话题

SpringBoot之分页查询的使用

背景 在业务中我们在前端总是需要展示数据&#xff0c;将后端得到的数据进行分页处理&#xff0c;通过pagehelper实现动态的分页查询&#xff0c;将查询页数和分页数通过前端发送到后端&#xff0c;后端使用pagehelper&#xff0c;底层是封装threadlocal得到页数和分页数并动态…