我们知道,同步锁无非是多个线程抢占一个资源,如果抢占成功就获得了锁,失败的线程则阻塞等待,直到获取到锁被释放再重新抢。貌似该过程就只有一种情况 ,但是观察上面的对象头结构,会发现里面标记了偏向锁、轻量级锁和重量级锁三种,又表示什么呢?

其实在jdk6之前,synchronized确实只有一种方式——重量级锁,其基本原理是使用底层操作系统的MutexLock来实现,该过程会把当前线程挂起,并从用户态切换到内核态。其问题是开销太大、性能不足,因为很多场景可能不需要这么重的操作。这在生活中也很常见,例如,如果平时买火车票,你只要提前一两天订好就行了,如果买春节的票就需要召唤小伙伴和七大姑八大姨,让他们一起帮你抢。前者竞争没那么大,我们使用轻量级操作就行了,而后者竞争激烈,需要使用大招。

从JDK6开始,synchronized做了很多优化工作,其中就包括上说的三种锁,其核心设计理论就是如何让线程在不阻塞的情况下保证线程安全。本节我们先看一下三种锁的特征,后面再分析其原理。

1 偏向锁

偏向锁就是在没有竞争时加的锁!这句话是不是很奇怪,没有竞争为什么要加锁?这是因为这种场景可能存在竞争的情况,我们加锁是为防范,就像上面说的买票的例子,一开始我们怕抢不到,就召唤一群人来帮忙,但是实际可能当天就没人买票,这样确实能防范可能存在的竞争问题,但是代价太大,因此我们使用代价较低的偏向锁来解决。这种按照最坏情况来处理的锁也称为“悲观锁”。

而偏向锁是“乐观思维”,思想是,线程再没有竞争的情况下访问资源时,会先通过CAS方式来抢占资源,如果成功则修改对象头的标记,也就是昭告天下“这个对象是我的”,具体操作是将偏向锁标记修改为1,锁标记修改为01,并将线程ID写入到对象中。这样其他线程再访问,发现这个对象已经被其他的抢占了,就只能先阻塞一段时间再去抢占。

那为啥叫“偏向锁”呢?假如线程A抢到了资源,如果线程B再抢则会被阻塞,但如果线程A再次抢占呢?例如代码里出现对某个资源嵌套加锁,此时该让A阻塞吗?不是的,因为资源就被A的还没释放,A再次抢占就应该直接放行。这是不是对其他线程不公平呢?是的,因此这种机制叫做偏向锁,偏向早就获得资源的锁。

我们看一个偏向锁的例子:

public class BiasedLockExample {public static void main(String[] args) throws InterruptedException {BiasedLockExample example=new BiasedLockExample();System.out.println("加锁之前");System.out.println(ClassLayout.parseInstance(example).toPrintable());synchronized (example){System.out.println("加锁之后");System.out.println(ClassLayout.parseInstance(example).toPrintable());}}
}

在上述代码中,BiasedLockExample演示了针对example这个锁对象,在加锁之前和之后分别打印的内存对象布局,我们看一下:

part_b_inside.chapter2_synchronzied.BiasedLockExample object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

在加锁之前,我们发现对象头中的第一个字节00000001最后三位001,其中低位的两位数表示锁标记,它的值是[01],表示当前为无锁状态。

加锁之后的我们再看一下:

part_b_inside.chapter2_synchronzied.BiasedLockExample object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           90 79 bc 0c (10010000 01111001 10111100 00001100) (213678480)4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

在加锁之后,我们发现对象头中的第一个字节10010000最后000,其中低位的两位数表示锁标记,它的值是[00],表示轻量级锁。

这里不存在竞争,应该是偏向锁,那为什么是轻量级锁呢。网上说是因为偏向锁开启会先延迟4秒,需要添加参数

-XX:BiasedLockingStartupDelay=0

然后再看:

此时看到第一个字节为00000101,末尾三位是101,第一个1表示偏向锁,后面的01表示当前是偏向锁状态。

我们可以将偏向锁的执行过程归纳如下:

补充

如果你调试一下上面的例子,就会发现加锁之前打印的结果第一个字节也是00000101,但是此时并没有加锁。一种解释是此时标记表示是可偏向的状态。

2 轻量级锁

偏向锁是没有竞争时获得锁资源,这种方式比单纯的加锁性能要高,但是如果此时真的有多个线程来竞争了该让竞争失败的先阻塞,直到被唤醒再重新抢锁。那是否有效率更高的方法呢? 我们可以让没有抢到资源的线程进行一定次数的重试(自旋转),例如重复抢3次或者5次等等。如果抢到了就不用重试了, 否则继续阻塞。这就是轻量级锁。 上面进行一定次数的重试的过程就叫自旋锁,也是基于CAS方式实现的。

当然自旋的次数不是没有限制的,一般是10次,而且JVM还会执行自适应的策略来优化。

轻量级锁也可以使用上面的BiasedLockExample来展示,将参数BiasedLockingStartupDelay取消掉展示的就是轻量级锁。

3 重量级锁

轻量级锁只适合在较短的时间里能获得锁的场景,如果长时间获取不到就不能一直自旋了,因为此时线程还占用了资源,但是什么都做不了,因此自旋到一定次数之后就要让其阻塞。

从上面的例子可以看到,如果在偏向锁、轻量级锁这些类型中无法让线程获得锁资源,那么这些没获得锁的线程最终的结果仍然是阻塞等待,直到获得锁的线程释放锁之后才能被唤醒,而在整个优化过程中,我们通过乐观锁的机制来保证线程的安全性。

下面这个例子演示了在加锁之前、单个线程抢占锁、多个线程抢占锁的场景,对象头中的锁的状态变化。

public class HeavyLockExample {public static void main(String[] args) throws InterruptedException {HeavyLockExample heavy=new HeavyLockExample();System.out.println("加锁之前");System.out.println(ClassLayout.parseInstance(heavy).toPrintable());Thread t1=new Thread(()->{synchronized (heavy){try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();//确保t1线程已经运行TimeUnit.MILLISECONDS.sleep(500);System.out.println("t1线程抢占了锁");System.out.println(ClassLayout.parseInstance(heavy).toPrintable());synchronized (heavy){System.out.println("main线程来抢占锁");System.out.println(ClassLayout.parseInstance(heavy).toPrintable());}System.gc();System.out.println(ClassLayout.parseInstance(heavy).toPrintable());}
}

结果比较长,我们不再贴出来,读者可以从中看到锁状态的逐步升级。

9.synchronized的三把锁相关推荐

  1. java让线程空转_Java锁:悲观/乐观/阻塞/自旋/公平锁/闭锁,锁消除CAS及synchronized的三种锁级别...

    JAVA LOCK 大全 [TOC] 一.广义分类:乐观锁/悲观锁 1.1 乐观锁的实现CAS (Compare and Swap) 乐观锁适合低并发的情况,在高并发的情况下由于自旋,性能甚至可能悲观 ...

  2. 并发编程 Java 三把锁(Synchronized、ReentrantLock、ReadWriteLock)

    Synchronized synchronized 的 3 种用法: 指定加锁对象(代码块):对给定对象加锁,进入同步代码前要获得给定对象的锁. void resource1() {synchroni ...

  3. Java 对synchronized的补充Lock锁

    Java并发编程:Lock 从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock. 也许有朋友会问,既然都可以通过synchr ...

  4. synchronized关键字以及实例锁 类锁

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...

  5. 死磕Synchronized底层实现--偏向锁

    注:本篇很长,请找个舒适的姿势阅读. 本文为synchronized系列第二篇.主要内容为分析偏向锁的实现. 偏向锁的诞生背景和基本原理在上文中已经讲过了,强烈建议在有看过上篇文章的基础下阅读本文. ...

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

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

  7. Synchronized原理(偏向锁篇)

    Synchronized原理(偏向锁篇) 传统的锁机制 传统的锁依赖于系统的同步函数,在linux上使用mutex互斥锁,最底层实现依赖于futex,这些同步函数都涉及到用户态和内核态的切换.进程的上 ...

  8. java中synchronized的三种写法详解

    预备知识 首先,我们得知道在java中存在三种变量: 实例变量 ==> 存在于堆中 静态变量 ==> 存在于方法区中 局部变量 ==> 存在于栈中 然后,我们得明白,合适会发生高并发 ...

  9. 学习笔记day12 synchronized底层实现及锁升级机制

    原博客:https://blog.csdn.net/weixin_40394952/article/details/118693945 一.synchronized使用方法 1.修饰实例方法,对当前实 ...

最新文章

  1. spring怎么解耦_终于有人把Spring和SpringMvc讲透了!
  2. Ubuntu 18.04 搭建 gitlab服务器记录
  3. 蓝桥杯2015初赛-三羊献瑞-枚举
  4. UVA - 442:Matrix Chain Multiplication
  5. startuml动态模型工具_动态面板模型估计方法简介以及stata应用
  6. Dubbo入门之hello world(zookeeper做注册中心)
  7. iOS - Swift NSRect 位置和尺寸
  8. 解决移动端 footer fixd 定位被键盘顶起来的方案
  9. android DVM
  10. TOGAF ADM 架构开发方法概述以及各个阶段的目的和交付物
  11. 017指北与游移方位惯导系统知识梳理
  12. retainAll用法
  13. BUAA(2021春) 北京地铁乘坐线路查询——Dijkstra和Floyd双解法
  14. 囚徒困境、智猪博弈、纳什均衡与一致预期(博弈论入门学习笔记二)
  15. 第五篇:风控模型监控预警
  16. ctf.show 萌xin区杂项
  17. Linux CentOs7 ping网址 未知的名称或服务
  18. 美团程序员提问:应届生,工资34k,算多吗?
  19. Faster RCNN训练FLIR红外线数据集
  20. Oracle聚簇因子(Clustering factor,CF)

热门文章

  1. 这8种实习生的转正几率为0%
  2. Swift源码简单解读-Map与FlatMap
  3. 1114Selenium web自动化测试经验分享-设置网页超时加载时间set_page_load_timeout()
  4. Three.js实战项目 商场漫游
  5. 手把手教你搭个Frida + Sekiro Rpc框架
  6. 鸟哥私房菜基础系列第3篇
  7. word2013如何全选全部页面上的图形(形状)?
  8. 公式编写1000问16-20
  9. 企业高效管理合同,微鳄365合同管理系统来助力
  10. [原创歌词]网络爱人