关于java:Java并发sychronized关键字

3次阅读

共计 2234 个字符,预计需要花费 6 分钟才能阅读完成。

大家好,这里是 淇妙小屋 ,一个分享技术,分享生存的博主
以下是我的主页,各个主页同步更新优质博客,创作不易,还请大家点波关注
掘金主页
知乎主页
Segmentfault 主页
开源中国主页
后续会公布更多 MySQL,Redis,并发,JVM,分布式等面试热点常识,以及 Java 学习路线,面试重点,职业规划,面经等相干博客
转载请表明出处!

1. synchronized 实现前提

1.1 对象监视器 monitor

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

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

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

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 ;
  }

外围字段

  • _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无关

1.4 栈帧中的锁记录

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

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

2. 锁降级

JDK1.6 前,sychronzied 是重量级锁

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

2.1 锁降级过程

  • 偏差锁

    持有偏差锁的线程应用完不会被动开释锁

    下一个线程尝试获取偏差锁,会尝试通过 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 不行
正文完
 0