synchronized中的锁

synchronized中的锁

1.1 Java对象头

以32位虚拟机为例

普通对象的对象头包括下面两个部分

Object Header(64 bits)
Mark Word(32 bits) Klass Word(32 bits)

数组对象的对象头包括下面三个部分

Object Header(96 bits)
Mark Word(32 bits) Klass Word(32 bits) array length(32 bits)

其中Mark Word 结构为

Mark Word(32 bits) State
hashcode:25 | (分代年龄)age:4 | (偏向锁是否启用)biased_lock:0 | 01 Normal
thread:23 | epoch:2 | age:4 | biased_lock:0 | 01 Biased
ptr_to_lock_record:30 | 00 Lightweight Locked
ptr_to_heavyweight_monitor:30 | 10 Heavyweight Locked
11 Marked for GC

1.2 Monitor(锁)工作原理

Monitor被翻译为监视器或管程

每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后
该对象头的Mark Word 中就被设置指向Monitor对象的指针

monitor结构如下

synchronized中的锁_第1张图片

那和java对象有什么关系呢?

  • 每一个java对象都会关联一个Monitor对象(当调用synchronized关键字尝试给对象加锁时就会关联)

    • synchronized中的锁_第2张图片
  • 当线程进入synchronized代码块时,Markword会指向Monitor,Owner的值就会是当前线程

  • 如果此时再来一个Thread1时,发现Owner有值,Thread1就获取不了锁

  • Thread1此时就会进入EntryList,可以理解为阻塞队列,Thread1进入阻塞状态

    • synchronized中的锁_第3张图片
  • Thread2执行完成后,就会Owner指向EntryList里面某个线程

注意:

  • synchronized 必须是进入同一个对象的monitor才有上述的效果

  • 不加synchronized 的对象不会关联监视器,不遵从以上规则

1.3 synchronized 优化原理

1.3.1 轻量级锁

  • 轻量级锁的使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

  • 轻量级锁对使用者是透明的,即语法仍然是synchronized

  • 假设有两个方法同步块,利用同一个对象加锁

    • static final Object obj = new Object();
      public static void method1(){
          synchronized (obj){
              method2();
          }
      }
      
      private static void method2() {
          synchronized (obj){
      
          }
      }
      
    • 创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word

      • synchronized中的锁_第4张图片
    • 进入menthod1中的synchronized时,让锁记录中Object reference指向锁对象,并尝试用cas替换Object的Mark Word,将Mark Word的值存入锁记录(00表示轻量级锁,上面表格中写过)

      • synchronized中的锁_第5张图片
    • 如果cas替换成功,对象头中存储了锁记录地址和状态 00,表示由该线程给对象加锁,这时图示如下

      • synchronized中的锁_第6张图片
    • 如果cas失败,有两种情况

      • 如果是其它线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程(后面会提到)

      • 如果是自己执行了synchronized锁重入,那么再添加一条Lock Record作为重入的计数

        • synchronized中的锁_第7张图片

        • 这里就是说明了synchronized是可重入锁

    • 当退出synchronized代码块(解锁时)如果有取值为null的锁记录,表示有重入,这时重置锁记录
      表示重入计数减一

    • 当退出synchronized代码块(解锁时)锁记录的值不为null,这时使用cas将Mark Word的值恢复给对象头

      • 成功,则解锁成功
      • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

1.3.2 锁膨胀

  • 如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。
static final Object obj = new Object();
public static void method1(){
    synchronized (obj){
        method2();
    }
}
  • 当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁

    • synchronized中的锁_第8张图片
  • 这时Thread-1加轻量级锁失败,进入锁膨胀流程

    • 即为object对象申请 Monitor锁,让object指向重量级锁地址
    • 然后自己进入Monitor的EntryList BLOCKED
    • synchronized中的锁_第9张图片
  • 当Thread-O退出同步块解锁时,使用cas将Mark Word的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中BLOCKED线程

这里就和前面的知识串联起来了

1.3.3 自旋优化

  • 重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
  • 自旋重试成功的情况:(适用于多核CPU)
线程1 对象Mark 线程2
- 10(重量锁) -
访问同步块,获取monitor 10(重量锁)重量锁指针 -
成功(加锁) 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 访问同步块,获取monitor
执行同步块 10(重量锁)重量锁指针 自旋重试
执行完毕 10(重量锁)重量锁指针 自旋重试
成功(解锁) 01(无锁) 自旋重试
- 10(重量锁)重量锁指针 成功(加锁)
- 10(重量锁)重量锁指针 执行同步块
-
  • 自旋重试失败的情况:
线程1 对象Mark 线程2
- 10(重量锁) -
访问同步块,获取monitor 10(重量锁)重量锁指针 -
成功(加锁) 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 访问同步块,获取monitor
执行同步块 10(重量锁)重量锁指针 自旋重试
执行同步块 10(重量锁)重量锁指针 自旋重试
执行同步块 10(重量锁)重量锁指针 自旋重试
执行同步块 10(重量锁)重量锁指针 阻塞
-
  • 在Java 6之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
  • 自旋会占用CPU时间,单核CPU自旋就是浪费,多核CPU自旋才能发挥优势。
  • Java 7之后不能控制是否开启自旋功能

1.3.4 偏向锁

  • 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行CAS操作
  • Java 6中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的 Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只要不发生竞争,这个对象就归该线程所有
  • 例如:
static final Object obj = new Object();
public static void method1(){
    synchronized (obj){
        method2();
    }
}

private static void method2() {
    synchronized (obj){

    }
}
private static void method3() {
    synchronized (obj){

    }
}

synchronized中的锁_第10张图片

synchronized中的锁_第11张图片

你可能感兴趣的:(java基础,java)