synchronized详解

synchronized实现原理

  • 修饰普通方法,锁的是当前的实例对象
  • 静态同步方法,锁的是当前类的class对象
  • 修饰代码块,锁的是括号中的实例对象

java对象头 monitor

对象头

synchronized 用的锁都是java对象头中的.hotpot虚拟机中对象头主要包括两部分数据:mark word(标记字段)和klass Point(类型指针).类型指针是指向它类元数据的指针,虚拟机通过这个指针来确定对象属于那个类的实例.标记字段用来存储对象自身的运行时数据,是实现偏向锁和轻量级锁的关键.
标记字段:
用于存储对象自身的运行时数据,如哈希码(HashCode) GC分代年龄 锁状态标志 线程持有的锁 偏向线程ID 偏向时间戳等.
考虑到虚拟机的空间效率问题,标记字段被设计成一个非固定的数据结构以便在极小的空间存储更多的数据.会根据对象的状态复用自己的存储空间.

monitor

monitor可以理解为一个同步工具,或者说是一种同步机制,被描述为一个对象.
所有的java对象是天生的Monitor,每个对象都有成为Monitor的潜力,在java设计中,每个java对象天生有一把看不见的锁,叫内部锁或Monitor锁.
Monitor锁是线程私有的数据结构,每个线程有一个monitor record列表,还有一个可用的全局列表每个被锁住的对象都会和一个monitor关联.同时monitor中有一个Owner字段 存放拥有该锁的线程的唯一标识,标识该锁被这个线程占用.

  • 每个对象都有一个monitor(监视器锁).当monitor被占用时就会处于被锁定状态,在线程进入被同步的代码块时,会首先请求获取monitor的所有权,获取不到则会阻塞.

    owner 初始为NULL,标识没有任何线程拥有该monitor,线程成功拥有该锁保后存该线程唯一标识,释放之后会有设置为NULL
    EntryQ: 关联一个系统互斥锁,阻塞所有试图获得monitor失败的线程
    RcThis 表示阻塞或waiting在该monitor上的线程个数
    Nest 用来实现可重入锁的计数
    HashCode 保存从对象头上copy过来的哈希码(可能还有GC age)
    Candidate 用来避免不必要的阻塞或等待线程唤醒,每次只能有一个线程能成功用有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换,浪费系统开销,.Candidate只有两种值,0表示没有需要唤醒的线程,1 表示要唤醒一个继任线程来竞争锁.

加锁过程

  1. 轻量级加锁
    (1). 在代码进入同步块时候,如果对象锁状态为无所状态,,虚拟机在栈中建立一个名为锁记录(Lock record)的空间,用于存储对象当前mark word字段,
    (2). 拷贝成功后,会通过CAS操作尝试将对象的mark word 更新为指向(lock record)的指针,并且将lock record的owner指针指向Object mark word,
    (3).如果更新成功,说明这个线程拥有了该对象的锁,并且将mark word的标志位设置为00,表示此对象处于轻量级锁定状态
    (4). 如果更新不成功,首先会检查mark word是否指向的是当前线程的栈帧,如果是说明线程拥有锁,如果不是,说明多个线程竞争锁,会膨胀为重量级锁,设置锁标志位为10,mark word存储指向重量级锁的指针,后面获取锁的都会进入阻塞状态
  2. 轻量级解锁:
    (1). 通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。
    (2). 如果替换成功,同步完成
    (3). 如果不成功变为重量级锁,在释放锁的时候唤醒被挂起的线程
  3. 偏向锁加锁
    1. 访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01——确认为可偏向状态
    2. 如果为可偏向状态,则测试线程ID是否指向当前线程.如果是,进入(5),不是进入(3)
    3. 如果线程ID并未指向当前线程,则通过CAS操作竞争锁。(这里CAS替换是换mark 替word中的线程ID,如果没有竞争,则会成功,如果有竞争,肯定有一个线程失败,这个失败的线程则会将锁升级为轻量级锁).如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5),如果竞争失败,执行(4)。
    4. 如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码
    5. 执行同步代码
  4. 偏向锁解锁
    解锁主要就是到达一个全局安全点,首先暂停拥有偏向锁的线程,然后检查拥有偏向锁的线程是否或者,如果不活着,则设置为无锁状态,如果活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,占中的锁记录和对象头的mark word要么重新偏向于其他线程,要么恢复到无锁活着标记对象不合适作为偏向锁,最后唤醒暂停的线程.