深入理解synchronize

本文参考引用,本人整理个人理解。地址点击

1.实现原理

synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。

下面是一些同步的基础

  1. 普通同步方法,锁是当前实例对象;
  2. 静态同步方法,锁是当前类的class对象;
  3. 同步方法块,锁是括号里面的对象

下面来看看一些代码进行分析

public class SynchronizeTest {

    public synchronized void method1() {
        System.out.println(" synchronized method1 ");
    }

    public void method2() {
        synchronized (this) {
            System.out.println(" synchronized block ");
        }
    }

}

javap解析class文件后如下

 public synchronized void method1();
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String  synchronized method1
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

  public void method2();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: monitorenter // 获取锁
       4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: ldc           #5                  // String  synchronized block
       9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: aload_1
      13: monitorexit   // 释放锁
      14: goto          22
      17: astore_2
      18: aload_1
      19: monitorexit  // 异常情况等也需要释放锁
      20: aload_2
      21: athrow
      22: return

上面可以看出method2的code的3和13分别对应synchronize监视器的进入获取锁和退出释放锁。
说明:同步代码块是使用monitorenter和monitorexit指令实现的,同步方法(在这看不出来需要看JVM底层实现)依靠的是方法修饰符上的ACCSYNCHRONIZED实现。

  • 同步代码块

    monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁;

  • 同步方法

    synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的accessflags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。
    (摘自:http://www.cnblogs.com/javaminer/p/3889023.html)

2.对象头

HotSpot虚拟机中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。其中对象头包括两部分:Mark Word 和 类型指针。

Mark Word

用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,占用内存大小与虚拟机位长一致。Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit),另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。

类型指针

对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说查找对象的元数据信息并不一定要经过对象本身。

image

其中锁标示位就是我们需要关注的

3、Monitor

什么是Monitor?


我们可以把它理解为一个同步工具,也可以描述为一种同步机制,它通常被描述为一个对象。 

与一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。 

Monitor 是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。

Monitor 是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。

其结构如下:
image

  • Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL。

  • EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程。

  • RcThis:表示blocked或waiting在该monitor record上的所有线程的个数。

  • Nest:用来实现重入锁的计数。HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)。

  • Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。
    Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

摘自:Java中synchronized的实现原理与应用

4.锁优化

java se 1.6以后对synchronize进行了优化。使其看起来没那么重
posted @ 2018-08-23 11:53  吃海苔的winton  阅读(158)  评论(0编辑  收藏  举报