深入理解synchronize
Posted ywd979
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解synchronize相关的知识,希望对你有一定的参考价值。
1.实现原理
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。
下面是一些同步的基础
- 普通同步方法,锁是当前实例对象;
- 静态同步方法,锁是当前类的class对象;
- 同步方法块,锁是括号里面的对象
下面来看看一些代码进行分析
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对象的大小,但是从数组的元数据中无法确定数组的大小。
类型指针
对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说查找对象的元数据信息并不一定要经过对象本身。
其中锁标示位就是我们需要关注的
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字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。
其结构如下:
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进行了优化。使其看起来没那么重
以上是关于深入理解synchronize的主要内容,如果未能解决你的问题,请参考以下文章
深入理解Java中的同步静态方法和synchronized(class)代码块的类锁