Synchronized底层原理

简介: Synchronized底层原理

本文讲述Synchronized关键字的使用和底层原理,我们使用Synchronized主要是为了保护共享资源在多线程修改的时候,会出现相互覆盖的问题,导致数据错乱。

一.使用

synchronized关键字用在方法级别,也可以用在方法代码上,用在方法代码块或方法级别时,可以作用于对象或者类,如下所示。

//用在方法上,类级别
    public static synchronized void inc(){
        a++;
    }
    //用在方法上,对象级别
    public synchronized void inc(){
        a++;
    }
    public  void inc2(){
        System.out.println("我是java小面");
        //synchronized作用于对象
        synchronized (lock) {
            a++;
        }
    }
    public  void inc3(){
        System.out.println("我是java小面");
        //synchronized作用于类
        synchronized (SynchronizedTest.class) {
            a++;
        }
    }
}

用在方法级别时

  • 非静态方法,锁的范围是同一个对象,不同对象互不影响
  • 静态方法,锁的访问是这个类创建的所有对象,是以类级别的锁。

用在方法代码块时:

  • 作用于对象,跟方法级别时一样的效果,只是比方法级别锁的粒度更细,在日常开发中推荐这种方法,尽可能的缩小锁的访问,提升性能
  • 作用于类,锁的访问是这个类创建的所有对象,是以类级别的锁。

二.原理

接下来讲解一下Synchronized的底层原理,jdk1.6之前,Synchronized锁是用操作系统的Mutex Lock来实现的,每次加锁和解锁操作都需要用户态到内核态的切换,切换代价是十分高的,导致1.6之前Synchronized称为重量锁;1.6之后使用了各自优化,使得Synchronized锁的性能得到了很大的提升跟reentrantlock是一样的,我们来一起看一下Synchronized的优化原理吧。

锁升级

Synchronized的锁的级别:偏向锁,轻量锁,重量锁

Synchronized的锁时存储在对象头里面的,所以我们先来看一下对象内存分布:对象头+ 实例数据 + 对齐填充

对象头信息数据结构:Mark Word + 对象类型指针 + [数组长度]

Mark Word存储锁的相关信息,结构如下:


偏向锁:

偏向锁:简单来说就是把线程ID设置到mark world里,适用于只有一个线程获取锁的场景。

当一个线程Synchronized加锁是,先查看一下对象mark world的标志位是否01,是就进行CAS把线程ID设置到设置到mark world,下次进入同步块时,只需要判断一下mark world是否设置的是当前线程ID,如果是直接进入。可以看到偏向锁只需要一次CAS操作,后续同一个线程只需要判断一下,对于只有没有线程竞争的同步代码块来说,提升的性能是非常可观的。

偏向锁撤销:当有别的线程竞争进入同步代码块时,就需要把偏向锁先撤销;在安全点才会执行这个操作,安全点的时候,线程都会暂停。这时候会两种情况

  • 原线程还在执行同步代码块,将对象锁升级为轻量锁,原线程继续持有
  • 原线程不在执行同步代码块,如果允许重偏向,则会偏向锁执行新的线程;如果不允许重偏向,将锁升级为轻量锁,新线程CAS竞争轻量锁。


轻量锁:

如果Mark world锁标记不是01,就会直接进行轻量锁的竞争;或者当偏向锁已被其他线程占用时,就会把偏向锁先撤销,然后再升级为轻量锁。轻量锁是指加锁和解锁都需要一次CAS操作,对不同的线程交替获取锁的场景,不同线程获取锁时间时不会有冲突的时候,性能也是非常高的

过程:

  • ** **先把Mark world拷贝到线程的栈帧里
  • CAS操作把Mark world头设置成指向线程的栈帧的指针,设置成功就代表加锁成功
  • CAS操作把Mark world设置会原来存储的内容,设置成功代表解锁成功,否则说明有竞争,进行锁升级,升级为重量锁。

image.png


重量锁:

升级为重量锁后,获取锁是操作系统的Mutex Lock,需要进行用户态和内核态的相互切换,性能会收到很大的影响。获取不到锁的线程会阻塞等待,直到其他线程释放锁。

锁优化


自旋锁与自适应自旋:

自旋锁:当获取不到锁时,会适当的进行尝试多次获取锁,但是会占用CPU

自适应自旋:会结合上次自旋是否成功获取锁的情况,智能设置自旋次数和自旋时间,以及是否使用自旋。

锁消除:

编译器会自动把一些无用的锁消除掉,比如下面代码:

public  void unNeedlock(){
        //每次都新建一个对象,相当于没有锁,编译器会自动删除该锁
       Object o = new Object();
        synchronized (o) {
            a++;
        }
}


锁粗化:

如果一段代码连续操作都对同一个对象反复加锁和 解锁,甚至在循环体中出现了加锁操作是,那即使没有线程竞争,频繁地进行互斥同步操作也会带来性能损耗。编译器会自动把锁方位粗化到循环体外。

public  void unNeedlock2(){
        for(int i=0;i<100;i++) {
            synchronized (lock) {
                a++;
            }
        }
    }
//优化后,只需加锁和解锁一次
   public  void unNeedlock2(){
        synchronized (lock) {
            for(int i=0;i<100;i++) {
                a++;
            }
          }
    }


三.总结

我们讲解了synchronized关键字的使用和它的底层实现,怎么进行锁升级以及编译器对它的一些优化,你学会了吗?

相关文章
|
缓存 NoSQL Java
Spring Cache 缓存原理与 Redis 实践
Spring Cache 缓存原理与 Redis 实践
513 0
|
12天前
|
消息中间件 NoSQL Java
spring boot2升级boot3指南
本文介绍了如何将Spring Boot 2.x升级至Spring Boot 3.x,涵盖使用OpenRewrite自动化重构工具进行代码转换、依赖版本升级、配置属性调整及常见问题处理等内容,帮助开发者高效完成升级工作。
307 6
|
数据采集 数据可视化 数据挖掘
爬虫技术对携程网旅游景点和酒店信息的数据挖掘和分析应用
爬虫技术是一种通过网络爬取目标网站的数据并进行分析的技术,它可以用于各种领域,如电子商务、社交媒体、新闻、教育等。本文将介绍如何使用爬虫技术对携程网旅游景点和酒店信息进行数据挖掘和分析,以及如何利用Selenium库和代理IP技术实现爬虫程序
1164 0
|
6月前
|
编解码 安全 Java
如何在Spring Boot中实现数据加密
本文介绍了如何在Spring Boot中实现数据加密。首先阐述了数据加密的重要性与应用场景,接着讲解了对称加密和非对称加密的原理及区别。通过添加依赖、配置加密算法、编写加密工具类等步骤,展示了具体实现方法,并在业务代码中应用加密技术保护敏感数据。希望对开发者有所帮助。
532 7
|
应用服务中间件 Linux nginx
让 CentOS 定时重启 Nginx
在CentOS上设置Nginx定时重启可通过`cron`或`systemctl`
584 0
|
10月前
|
前端开发
使用 Promise.all 处理多个 Promise 同时失败的情况
【10月更文挑战第26天】通过以上几种方法,我们可以根据具体的需求和环境选择合适的方式来处理多个 `Promise` 同时失败的情况,从而更好地进行错误处理和程序的稳定性保障。
780 2
|
存储 安全 Java
(二) 彻底理解Java并发编程之 Synchronized关键字实现原理剖析
Synchronized 关键字(互斥锁)原理,一线大厂不变的面试题,同时也是理解 Java 并发编程必不可少的一环!其中覆盖的知识面很多,需要理解的点也很多,本文会以相关书籍和结合自己的个人理解,从基础的应用范围到底层深入剖析的方式进行阐述,如果错误或疑问欢迎各位看官评论区留言纠正,谢谢!
391 0
|
存储
el-tree 动态指定默认选中节点
el-tree 动态指定默认选中节点
919 6
|
存储 缓存 安全
【实战指南】轻松自研嵌入式日志框架,6大功能亮点一文读懂
本文介绍了如何自研一个嵌入式日志框架,涵盖了6大功能亮点:日志分级管理、异步处理与并发安全性、详尽上下文信息记录、滚动日志归档策略、高效资源利用和便捷API接口。设计上,通过日志过滤器、共享环形缓冲区和独立的日志管理进程实现日志管理。在并发环境下,使用信号量保证线程安全。日志文件按大小滚动并有序归档,同时考虑了资源效率。对外提供简洁的API接口,便于开发人员使用。文章还简述了实现细节,包括实时存储、日志滚动和共享内存管理。测试部分验证了日志回滚和实时打印功能的正确性。
503 4
|
JavaScript API
VUE——监听浏览器关闭及标签页关闭事件
VUE——监听浏览器关闭及标签页关闭事件
748 0