参考:java3y《对线面试官》

synchronized

synchronized是⼀种互斥锁,⼀次只能允许⼀个线程进⼊被锁住的代码块
synchronized是Java的⼀个关键字,它能够将代码块/⽅法锁起来
如果synchronized修饰的是实例⽅法,对应的锁则是对象实例
如果synchronized修饰的是静态⽅法,对应的锁则是当前类的Class实例
如果synchronized修饰的是代码块,对应的锁则是传⼊synchronized的对象实例

JDK1.6之前是重量级锁。

-- java3y《对线面试官》

底层原理

同步代码块是通过monitorenter和monitorexit来实现,当线程执行到monitorenter的时候要先获得monitor锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁

同步方法是通过设置ACC_SYNCHRONIZED标志来实现,当线程执行有ACC_SYNCHRONI标志的方法,需要获得monitor锁。

每个对象维护一个加锁计数器,为0表示可以被其他线程获得锁,不为0时,只有当前锁的线程才能再次获得锁。

同步方法和同步代码块底层都是通过monitor来实现同步的。

每个对象都与一个monitor对象相关联,线程可以占有或者释放monitor,monitor对象中存储着当前持有锁的线程以及等待锁的线程队列

内存中对象一般由三部分组成,分别是对象头、对象实际数据和对其填充,对象头里有Mark Word,Mark Word会记录对象关于锁的信息,有指针指向monoitor

--掘金社区 https://juejin.cn/post/6844903918653145102、  java3y

monitor是什么

可以理解为一种同步工具,或者说是同步机制,操作系统的管程是概念原理,ObjectMonitor是它的原理实现。

对象与monitor怎么关联

  • 对象里有对象头
  • 对象头里面有Mark Word
  • Mark Word指针指向了monitor

JDK1.6优化

JDK1.6之前 它加锁是依赖底层操作系统的 mutex 相关指令实现,所以会有⽤户态和内核态之间的切换,性能损耗⼗分明显

JDK1.6增加了适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等优化策略。在jvm层面实现加锁逻辑,不依赖底层操作系统,就没有切换的消耗

什么是用户态和内核态

内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。

用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。

区别就是对于资源的访问权限不一样

偏向锁

偏向锁指的就是JVM会认为只有某个线程才会执⾏同步代码(没有竞争的环境)

在Mark Word会直接记录线程ID,只要线程来执⾏代码了,会⽐对线程ID是否相等,相等则当前线程能直接获取得到锁,执⾏同步代码

如果不相等,则⽤CAS来尝试修改当前的线程ID,如果CAS修改成功,那还是能获取得到锁,执⾏同步代码

如果CAS失败了,说明有竞争环境,此时会对偏向锁撤销,升级为轻量级锁

轻量级锁

在轻量级锁状态下,当前线程会在栈帧下创建Lock Record,Lock Record 会把Mark Work的信息拷贝进去,且有个Owner指针指向加锁的对象

线程执行到同步代码时,则用CAS试图将Mark Word的指针指向线程栈帧的Lock Record,假设CAS修改成功,则获取得到轻量级锁

修改失败则自旋重试,自旋一定次数后,则升级为重量级锁

Lock Record

锁记录对象,存放在栈帧上,持有displaced word和锁住对象的元数据; 解释器使用lock record来检测非法的锁状态;

隐式地充当锁重入机制的计数器;

综述

synchronized锁原来只有重量级锁,依赖操作系统的mutex指令,需要用户态和内核态切换,性能损耗十分明显

重量级锁用到monitor对象,而偏向锁则在Mark Word记录线程ID进行比对,轻量级锁则是拷贝Mark Word到Lock Record,用CAS+自旋的

式获取

引入了偏向锁和轻量级锁,就是为了在不同的使用场景使用不同的锁,进而提高效率。锁只有升级,没有降级

只有一个线程进入临界区-》偏向锁

多个线程交替进入临界区-》轻量级锁

多线程同时进入临界区-》重量级锁

CAS

compare and swap,⽐较并交换是⼀个原⼦性的操作,对应到CPU指令为cmpxchg

有三个操作数:当前值A、内存值V、要修改的新值B

假设 当前值A 跟 内存值V 相等,那就将 内存值V 改成B

假设 当前值A 跟 内存值V 不相等,要么就重试,要么就放弃更新

将当前值与内存值进⾏对⽐,判断是否有被修改过,这就是CAS的核⼼

为什么要用CAS

synchronized锁每次只会让⼀个线程去操作共享资源

⽽CAS相当于没有加锁,多个线程都可以直接操作共享资源,在实际去修改的时候才去判断能否修改成功

在很多的情况下会比synchronized锁要⾼效很多

⽐如,对⼀个值进⾏累加,就没必要使⽤synchronized锁,使⽤juc包下的Atomic类就⾜以 (atomic包下采用的是CAS)

有什么缺点

会带来ABA的问题,因为只⽐对当前值和内存值是否相等

线程A拿到当前值10,线程B修改内存值为20,线程C又改回来,当线程A去判断当前值和内存值是否相等的时候,发现是相等的,线程A是可

以修改的,它觉得值没有发生过变化

解决:java提供了AtomicStampedReference类供我们⽤,就是引入版本,比对的是内存值+版本值是否一致

推荐使⽤ LongAdder 对象

阿⾥巴巴开发⼿册提及到 推荐使⽤ LongAdder 对象,⽐ AtomicLong 性能更好(减少乐观锁的重试次数)

AtomicLong做累加的时候实际上就是多个线程操作同⼀个⽬标资源,在⾼并发时,只有⼀个线程是执⾏成功的,其他的线程都会失败,不断⾃旋(重试),⾃旋会成为瓶颈

⽽LongAdder的思想就是把要操作的⽬标资源「分散」到数组Cell中,每个线程对⾃⼰的 Cell 变量的 value 进⾏原⼦操作,⼤⼤降低了失败的次数,这就是为什么在⾼并发场景下,推荐使⽤LongAdder 的原因

synchronized与CAS的更多相关文章

  1. Java并发(4)- synchronized与CAS

    引言 上一篇文章中我们说过,volatile通过lock指令保证了可见性.有序性以及"部分"原子性.但在大部分并发问题中,都需要保证操作的原子性,volatile并不具有该功能,这 ...

  2. 并发和多线程(二)--线程安全、synchronized、CAS简介

    线程安全性: 当多个线程访问一个类的时候,这个类始终表示出正确的行为,那么这个类是线程安全的. 无状态的对象一定是线程安全的,例如大部分service.dao.Servlet都是无状态的. 线程安全体 ...

  3. Java并发——CAS

    什么是CAS? CAS是Compare And Swap的简称.在Java中有很多实现,比如compareAndSwapObject()方法,或者compareAndSwapInt()方法等.多用在包 ...

  4. JUC--volatiley&CAS

    public class VolatileTest { public static void main(String[] args) { ThreadDemo td = new ThreadDemo( ...

  5. 多线程深入:让你彻底理解Synchronized(转)

    原文:https://www.jianshu.com/p/d53bf830fa09 1. synchronized简介 在学习知识前,我们先来看一个现象: public class Synchroni ...

  6. (六)彻底理解synchronized

    1.sychronized简介 在学习知识之前,我们先来看一个现象 public class SynchronizedDemo implements Runnable { private static ...

  7. java并发:CAS算法和ABA问题

    CAS算法是硬件对于并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令. CAS用于管理对共享数据的并发访问. java的并发包中,AQS.原子操作类等都是基于CAS实现的. CAS 是一种 ...

  8. 4.彻底理解synchronized

    1. synchronized简介 在学习知识前,我们先来看一个现象: public class SynchronizedDemo implements Runnable {    private s ...

  9. java并发之CAS详解

    前言 在高并发的应用当中,最关键的问题就是对共享变量的安全访问,通常我们都是通过加锁的方式,比如说synchronized.Lock来保证原子性,或者在某些应用当中,用voliate来保证变量的可见性 ...

  10. 这篇文章带你彻底理解synchronized

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

随机推荐

  1. Solon v2.1.4 发布。支持 java、kotlin、groovy!

    本次发布,重点测试和验证了在 java.kotlin.groovy 三种 jvm 语言里,开箱即用的特性.并发布 Solon Initializr: https://solon.noear.org/s ...

  2. LeetCode算法训练-动态规划

    欢迎关注个人公众号:爱喝可可牛奶 LeetCode算法训练-动态规划 理论知识 动态规划当前状态是由前一个状态推导出来的,而贪心没有状态的转移 动态规划需要借助dp数组,可能是一维也可能是二维的 首先 ...

  3. RESTful风格与Spring注解

    RESTfulL是一种网络应用程序的设计风格和开发方式,即接口请求方式和路径的一种风格. 普通风格: localhost:8080/add?a=1&b=2 RestFul风格: localho ...

  4. 鉄道旅行 (Railroad Trip)

    题意 有 \(n\) 个城市, \(n-1\) 条道路.其中第 \(i\) 个城市和第 \(i+1\) 个城市由第 \(i\) 条道路连接.通过一条道路有两种付费方式:每次支付费用 \(a_i\) , ...

  5. C# DevExpress GridControl中BandedGridView表格使用

    1.设计器方式创建 1.点击"Change view">>然后在选中"Convert to"选项>>最后选中点击"Banded ...

  6. 向mysql插入数据报错 pymysql.err.DataError: (1406, "Data too long for column 'class' at row 1") 解决方案

    这个问题一开始更换数据类型或者数据类型的大小,发现还是不行.后面通过网上查询了一条神奇的sql语句分分钟钟的解决了 问题原因明明是: 字段的长度不够存放数据 解决方案: 在mysql命令行输入如下:S ...

  7. 如果还有问ARKIME不会部署安装,你就把这篇丢给他!

    前言 关于在线安装和离线安装的配合方式,在本文中不再赘述.基本就是准备在线.离线两个一模一样的环境,在在线环境中边安装边借助yumdownloader.wget等解析依赖和下载安装包,然后再离线环境中 ...

  8. js区分图片加载中和加载完成状态

    var _ent = document.getElementById("test"); if (_ent.complete) { //图片已经加载完成 _ent.stop(); } ...

  9. IsNotEmpty和isNotBlank的区别

    isNotEmpty和isNotBlank都是判断字符串非空首先查看isNotEmpty( )isNotEmpty判断是否为空(null或size=0),不为空就返回true StringUtils. ...

  10. 分析网络工具 Wireshark与tcpdump

    一.安装使用 1. 安装 2. 选择网卡:我们的主机就是通过其中一块网卡和其他主机进行数据交互: 3. 点击开始:打开wireshark,点击左上角那个蓝色的鲨鱼鳍按钮,开始捕获新的分组并清空之前的分 ...