作者:jack_xu

juejin.im/post/5e898b8fe51d4546cd2fda30

为什么要用Synchronized

这个问题很简单,首先我们来看下面这个代码

开10000个线程,将变量count递增,结果是9998,很显然是出现了线程不安全。那为什么会出现这样的结果呢,答案也很简单

这里稍微解释下为啥会得不到 100(知道的可直接跳过), i++ 这个操作,计算机需要分成三步来执行。

  1. 读取 i 的值。

  2. 把 i 加 1.

  3. 把 最终 i 的结果写入内存之中。

所以,

(1)、假如线程 A 读取了 i 的值为 i = 0,
(2)、这个时候线程 B 也读取了 i 的值 i = 0。
(3)、接着 A把 i 加 1,然后写入内存,此时 i = 1。
(4)、紧接着,B也把 i 加 1,此时线程B中的 i = 1,然后线程B 把 i 写入内存,此时内存中的 i = 1。

也就是说,线程 A, B 都对 i 进行了自增,但最终的结果却是1,不是 2。

归根到底一句话就是这么多操作不是原子性,那怎么解决这个问题呢,加上Synchronized即可

三大特性

在上面例子演示的是原子性。synchronized 可以确保可见性,根据happens-before规定,在一个线程执行完 synchronized 代码后,所有代码中对变量值的变化都能立即被其它线程所看到。顺序性的话就是禁止指令重排,代码块中的代码从上往下依次执行,归根到底再一句话,并发问题中的三个特性synchronized都能保证,也就是synchronized是万金油,用他准没错!

使用方法

从语法上讲,Synchronized总共有三种用法:

修饰实例方法

public synchronized void eat(){..............
}

修饰静态方法

public static synchronized void eat(){..............
}

修饰代码块

public void eat(){synchronized(this){..............}
}
public void eat(){synchronized(Eat.class){..............}
}

其中第一种和第三种对等,第二种和第四种对等,这个很简单,下面是使用 synchronized的总结:

  • 选用一个锁对象,可以是任意对象;

  • 锁对象锁的是同步代码块,并不是自己;

  • 不同类型的多个 Thread 如果有代码要同步执行,锁对象要使用所有线程共同持有的同一个对象;

  • 需要同步的代码放到大括号中。需要同步的意思就是需要保证原子性、可见性、有序性中的任何一种或多种。不要放不需要同步的代码进来,影响代码效率。

锁升级

好,本文的高潮来了,大家仔细听,在JDK的早期,synchronized叫做重量级锁,因为申请锁资源必须通过kernel,系统调用,从用户态 -> 内核态的转换,效率比较低,JDK1.6 之后做了一些优化,为了减少获得锁和释放锁带来的性能开销,引入了偏向锁、轻量级锁的概念。因此大家会发现在 synchronized 中,锁存在四种状态分别是:无锁、偏向锁、轻量级锁、重量级锁;

我们知道synchronized锁的是对象,对象就是Object,Object在heap中的布局,如下图所示

前面8个字节就是markword,后面4个字节是class pointer就是这个对象属于哪个类的,People就是People.class,Cat类就是Cat.class,在后面实例数据就是看你类里面字段的具体大小了,int age就是4个字节,string name就是英文1个字节, 中文2个字节(String的中文字节数要看用的编码集合,如果是utf-8类型的,那么中文占2到3个字节,如果是GBK类型的,那么中文占2个字节),最后前面三项加起来不能被8整除的,就是补齐到能够被8整除。

下图就是markword(8*8=64位)的分布图,锁升级就是markdown里面标志位的变化。

网上所以的图都是32位的,我这里画的是64位的,大家发现一共有五种状态,用两位是不够的,所以01的时候在向前借一位。

偏向锁

hotspot虚拟机的作者经过调查发现,大部分情况下,加锁的代码不仅仅不存在多线程竞争,而且总是由同一个线程多次获得。所以基于这样一个概率,我们一开始加锁上的是偏向锁,当一个线程访问加了同步锁的代码块时,首先会尝试通过CAS操作在对象头中存储当前线程的ID

  • 如果成功markword则存储当前线程ID,接着执行同步代码块

  • 如果是同一个线程加锁的时候,不需要争用,只需要判断线程指针是否同一个,可直接执行同步代码块

  • 如果有其他线程已经获得了偏向锁,这种情况说明当前锁存在竞争,需要撤销已获得偏向锁的线程,并且把它持有的锁升级为轻量级锁(这个操作需要等到全局安全点,也就是没有线程在执行字节码)才能执行

在我们的应用开发中,绝大部分情况下一定会存在 2 个以上的线程竞争,那么如果开启偏向锁,反而会提升获取锁的资源消耗。所以可以通过jvm参数UseBiasedLocking 来设置开启或关闭偏向锁。偏向锁详细:死磕Synchronized底层实现--偏向锁

轻量级锁

撤销偏向锁,升级轻量级锁,每个线程在自己的线程栈生成LockRecord,用CAS操作将markword设置为指向自己这个线程的LR的指针,设置成功者得到锁。轻量级锁在加锁过程中,用到了自旋锁,自旋锁的使用,其实也是有一定条件的,如果一个线程执行同步代码块的时间很长,那么这个线程不断的循环反而会消耗 CPU 资源。轻量级详细:死磕Synchronized底层实现--轻量级锁

  • 默认情况下自旋的次数是 10 次,可以通过-XX:PreBlockSpin来修改,或者自旋线程数超过CPU核数的一半

  • 在 JDK1.6 之后,引入了自适应自旋锁,自适应意味着自旋的次数不是固定不变的,而是根据前一次在同一个锁上自旋的时间以及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源

满足这两种情况之一后升级为重量级锁

重量级锁

这时候就惊动老佛爷了,向操作系统申请资源,linux mutex , CPU从3级-0级系统调用,线程挂起,进入等待队列,等待操作系统的调度,然后再映射回用户空间。

我们随便写一段简单的带有 synchronized 关键字的代码。先将其编译为.class 文件,然后使用 javap -c xxx.class 进行反汇编。我们就可以得到 java 代码对应的汇编指令。里面可以找到如下两行指令。

字节码层面就是关键的这两条指令,monitorenter,moniterexit (注:代码块用的是ACC_SYNCHRONIZED,这是一个标志位,底层原理还是这两条指令)

java中每个对象都关联了一个监视器锁monitor,当monitor被占用时就会处于锁定状态。线程执行monitorenter 指令时尝试获取monitor的所有权,过程如下:

  • 如果monitor的进入数为 0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor 的所有者。

  • 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加 1。

  • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为 0,再重新尝试获取monitor的所有权。

从上面过程可以看出两点,第一:monitor是可重入的,他有计数器,第二:monitor是非公平锁

monitor 依赖操作系统的mutexLock(互斥锁)来实现的,线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能。重量级锁详细:死磕Synchronized底层实现--重量级锁

锁消除

我们都知道 StringBuffer 是线程安全的,因为它的关键方法都是被 synchronized修饰过的,但我们看上面这段代码,我们会发现,sb 这个引用只会在 add方法中使用,不可能被其它线程引用(因为是局部变量,栈私有),因此 sb是不可能共享的资源,JVM 会自动消除 StringBuffer 对象内部的锁。

public void add(String str1,String str2){StringBuffer sb = new StringBuffer();sb.append(str1).append(str2);
}

总结

好,本文对synchronized所涵盖的知识点已经讲解的很清楚了。synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。在synchronized优化以前,synchronized的性能是比ReentrantLock差很多的,但是自从synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了。

在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReentrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。

END

Java面试题专栏

【61期】MySQL行锁和表锁的含义及区别(MySQL面试第四弹)

【62期】解释一下MySQL中内连接,外连接等的区别(MySQL面试第五弹)

【63期】谈谈MySQL 索引,B+树原理,以及建索引的几大原则(MySQL面试第六弹)

【64期】MySQL 服务占用cpu 100%,如何排查问题? (MySQL面试第七弹)

【65期】Spring的IOC是啥?有什么好处?

【66期】Java容器面试题:谈谈你对 HashMap 的理解

【67期】谈谈ConcurrentHashMap是如何保证线程安全的?

【68期】面试官:对并发熟悉吗?说说Synchronized及实现原理

【69期】面试官:对并发熟悉吗?谈谈线程间的协作(wait/notify/sleep/yield/join)

【70期】面试官:对并发熟悉吗?谈谈对volatile的使用及其原理

我知道你 “在看”

大话Synchronized及锁升级相关推荐

  1. 第十二章:synchronized与锁升级

    相关面试题 锁优化背景 Synchronized 锁性能变化 jdk5 以前 复习:为什么任意一个对象都能成为锁? jdk6 之后 synchronized的种类以及锁升级流程 锁升级流程 无锁 偏向 ...

  2. synchronized与锁升级

    作者:bravo1988 链接:https://www.zhihu.com/question/317687988/answer/1715863550 来源:知乎 著作权归作者所有.商业转载请联系作者获 ...

  3. 简述Synchronized以及锁升级

  4. 12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁

    小陈:呼叫老王...... 老王:来了来了,小陈你准备好了吗?今天我们来讲synchronized的锁重入.锁优化.和锁升级的原理 小陈:早就准备好了,我现在都等不及了 老王:那就好,那我们废话不多说 ...

  5. Synchronized关键字和锁升级

    一.Synchronized 对于多线程不安全(当数据共享(临界资源),而多线程同时访问并改变该数据时,就会不安全),JAVA提供的锁有两个,一个是synchronized关键字,另外一个就是lock ...

  6. synchronized 底层如何实现?什么是锁升级、降级?

    synchronized 底层如何实现?什么是锁升级.降级? synchronized 代码块是由一对 monitorenter/monitorexit 指令实现的,Monitor 对象是同步的基本实 ...

  7. 存储过程没有执行完后没有释放锁_面试必问---synchronized实现原理及锁升级过程你懂吗?...

    synchronized实现原理及锁升级过程 前言: synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的 尽管最初synchronized的性能效率比较差 ...

  8. 在c#中用mutex类实现线程的互斥_面试官经常问的synchronized实现原理和锁升级过程,你真的了解吗...

    本篇文章主要从字节码和JVM底层来分析synchronized实现原理和锁升级过程,其中涉及到了简单认识字节码.对象内部结构以及ObjectMonitor等知识点. 阅读本文之前,如果大家对synch ...

  9. synchronized锁升级_synchronized详解以及锁的膨胀升级过程

    点击上方"码之初"关注,···选择"设为星标" 与精品技术文章不期而遇 来源:www.cnblogs.com/cxiaocai/p/12189848.html ...

最新文章

  1. spring之Environment
  2. ftp 工具_ftp,ftp工具哪个好用
  3. rpm 软件的安装和卸载
  4. SAP Commerce Cloud Product Review 的添加逻辑
  5. 代码实现sql编译器_【数据蒋堂】第 19 期:从 SQL 语法看集合化
  6. 宏定义#define
  7. 自定义的html radio button的样式
  8. 检查java_如何检查Java版本?
  9. 从Nest到Nesk -- 模块化Node框架的实践
  10. 七、MySQL中的字符集 - 系统的撸一遍MySQL
  11. c语言入门基础知识总结
  12. PreferenceScreen1
  13. 自动驾驶仿真相关调研
  14. Eclipse错误: 找不到或无法加载主类或项目无法编译10种解决大法!
  15. 论文清单:一文梳理因果推理在自然语言处理中的应用
  16. 关于使用idea输入中文时,候选框不出现在光标附近的问题
  17. 什么是 CI/CD?(翻译)
  18. 火狐浏览器打印时会出现边框线不显示(缩放页面也会出现)
  19. 数组-数字组合II-中等
  20. 演讲实录丨翁冬冬 新型虚拟现实体验形式及其在主题公园中的应用

热门文章

  1. 蔚来与雷蛇联合推出NIO ES6限量版车型 售价46.78万元
  2. 苹果高通关系紧张背后:不只是专利问题 还有两家公司CEO的私人恩怨
  3. 新能源补贴退坡 广汽新能源、比亚迪不涨价 蔚来最狠!
  4. cygwin汉化简单操作【ZT】
  5. 晨哥真有料丨自信一点!恋爱做自己,不要自卑,不要迎合!
  6. python 储蓄计划_365天储蓄计划表
  7. SVN、GIT生成版本号
  8. linux扩展根路径,Linux虚拟机根(/)目录扩容
  9. cas登录后怎么直接到我们系统_当我们购买服务器后,那么服务器的操作系统该怎么选择呢?...
  10. 为啥我从后台查到的值在页面显示的是undefined_【java笔记】046天,作购物车页面,学习JavaScript...