前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java并发—面试再也不怕synchronized,一文说透

Java并发—面试再也不怕synchronized,一文说透

原创
作者头像
淇妙小屋
修改2022-03-31 15:02:26
2300
修改2022-03-31 15:02:26
举报
文章被收录于专栏:面试题详解面试题详解

大家好,这里是淇妙小屋,一个分享技术,分享生活的博主

后续会发布更多MySQL,Redis,并发,JVM,分布式等面试热点知识,以及Java学习路线,面试重点,职业规划,面经等相关博客

转载请标明出处!

1. synchronized实现前提

1.1 对象监视器monitor

任何一个对象都有一个monitor对象与之关联

monitor提供线程阻塞和唤醒机制

当一个monitor被某个线程持有,它将处于锁定状态(获取锁)

代码语言:C++
复制
ObjectMonitor() {
    _header       = NULL;
    _count        = 0;  //锁计数器,sychronized是可重入锁
    _waiters      = 0,  
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL; //标识持有锁的线程
    _WaitSet      = NULL; //等待队列,与Object的wait()和notify()有关
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //同步队列,尝试获取锁的线程会进入EntryList,如果成功获得锁,则设置monitor的_owner为,如果没有成功获得锁,在EntryList中阻塞等待
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }
11
11

核心字段

  • _count:计数器,如果为0,表示没有线程获得锁,如果不为0,表示有线程取得锁(可以实现可重入锁,持有锁的线程每尝试再次获取锁,计数器都会+1)
  • _owner:标识持有锁的线程
  • _EntryList:一个线程如何尝试获取锁,会进入_EntryList,如果获得锁成功,将 _owner修改为自己;如果获得锁失败,则会在同步队列EntryList中阻塞等待
  • _waitSet:等待队列,与Object的wait()和notify()有关,如果一个线程在持有锁时,调用了其wait(),那么该线程会进入其_waitSet中,阻塞,并释放锁,知道有其他线程在持有锁时,调用其notify()将线程唤醒
1.2 JVM提供的指令

synchronized在JVM中通过 monitorenter指令monitorexit指令来进入和退出同步代码块

  • monitorenter指令

编译器会将monitorenter插入在同步代码块开始的位置,执行该指令时,会尝试获取通同步对象的monitor对象的所有权(尝试获得锁)

  • monitorexit指令

编译器会将monitorexit插入在同步代码块的结束处或异常处,释放同步对象的monitor对象的所有权(释放锁)

1.3 Mark Word

对象由三个部分组成——对象头,实例数据,对齐填充

对象头结构如下(非数组2个字,数组3个字)

  • 第一个字——Mark Word
  • 第二个字——指向对象Class对象的指针
  • 第三个字——数组长度

sychronized的实现与 Mark Word有关

11
11
1.4 栈帧中的锁记录

线程的栈帧中有一块空间——锁记录

  • 线程如果成果获取偏向锁,会在锁记录中存储偏向锁偏向的线程ID
  • 在尝试获取轻量级锁前,会把对象头的Mark Word复制到自己的锁记录中

2. 锁升级

JDK1.6前,sychronzied是重量级锁

JDK1.6做了优化,对锁进行了分类——无锁,偏向锁,轻量级锁,重量级锁

2.1 锁升级过程
11
11
  • 偏向锁

持有偏向锁的线程使用完不会主动释放锁

下一个线程尝试获取偏向锁,会尝试通过CAS将Mark Word中的线程ID替换为自己的,如果原来持有锁的线程已经结束了同步代码块,那么替换就会成功,下一个线程就会取得锁

  • 轻量级锁

持有锁的线程使用完后会尝试释放锁,如果释放失败,则升级为重量级锁

  • 重量级锁

会阻塞未获取锁的线程,除了重量级锁,其他情况下,未持有锁的线程不会阻塞

2.2 各个锁的优缺点

优点

缺点

适用场景

偏向锁

加锁和解锁不需要额外消耗

如果线程间存在锁竞争,撤销锁会带来额外消耗

适用于只有一个线程访问同步代码块的情况

轻量级锁

竞争锁的线程不会阻塞,加快响应速度

如果使用竞争不到锁,自旋会消耗CPU性能

追求响应速度

重量级锁

竞争锁的线程阻塞,不消耗CPU

线程阻塞,响应时间慢

追求吞吐量,同步代码快执行时间较长

3. sychronized的内存语义

  • 保证可见性
  • 编译器会将monitorenter指令插入在同步代码块开始的位置,当线程执行到该指令时,会尝试获取同步对象的monitor的所有权(尝试获取锁),如果获取成功,则成功获得锁——JMM会将线程对应的本地内存设置为无效,从而使得该线程执行同步代码块时必须从主内存读取共享变量
  • 编译器会将monitorexit指令插入在同步代码块的结束处或异常处,当线程执行到该指令时,会释放同步对象的monitor的所有权(释放锁),JMM会把线程本地内存中的副本刷新到主内存中
  • 保证原子性

只允许一个线程获得锁后执行同步代码块

  • 保证有序性

JMM不允许同步语句与非同步语句重排序,但是允许同步代码块内在不改变结果的前提下进行重排序(JMM虽然允许同步代码块内重排序,但是在程序员看来仍是有序的)

4. volatile与sychronized的区别

  1. volatile更轻量,不需要加锁,不会阻塞线程
  2. sychronized可以保证复合语句的原子性,volatile不行

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. synchronized实现前提
    • 1.1 对象监视器monitor
      • 1.2 JVM提供的指令
        • 1.3 Mark Word
          • 1.4 栈帧中的锁记录
          • 2. 锁升级
            • 2.1 锁升级过程
              • 2.2 各个锁的优缺点
              • 3. sychronized的内存语义
              • 4. volatile与sychronized的区别
              相关产品与服务
              云数据库 MySQL
              腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档