众所周知synchronized关键字是解决并发问题常用解决方案,有以下三种方式:
- 同步普通方法,锁的是当前对象。
- 同步精通方法,所得是当前
Class对象。 - 同步块,所得是
()中的对象。
实现原理:JVM是通过进入、退出对象监视器(Monitor)来实现对方法、同步块的同步的。具体实现是在编译之后在同步方法调用前加入一个monitor.enter指令,在退出方法和异常出插入monitor.exit的指令。其本质就是对一个对象监视器(Monitor)进行获取,而这个获取的过程具有排他性从而达到了同一时刻只能一个线程访问的目的。而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程monitor.exit之后才能尝试继续获取锁。
流程图如下:
5d313f638492c49210.jpg
通过一段代码来演示:
public static void main(String[] args) {
synchronized (Synchronize.class){
System.out.println("Synchronize");
}
}
使用javap -c Syncchronize可以查看编译之后的具体信息。
public class com.crossoverjie.synchronize.Synchronize {
public com.crossoverjie.synchronize.Synchronize();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // class com/crossoverjie/synchronize/Synchronize
2: dup
3: astore_1
**4: monitorenter**
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String Synchronize
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
**14: monitorexit**
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return
Exception table:
from to target type
5 15 18 any
18 21 18 any
}
可以看到在同步块的入口和出口分别有monitorenter,monitorexit指令。
锁优化
synchronized很多都称之为重量锁,JDK1.6中对synchronized进行了各种优化,为了能减少获取和释放锁带来的小号引入了偏向锁和轻量锁。
锁粗化
在使用synchronized时,JVM会对同一对象使用的锁进行优化,比如StringBuffer的append方法是synchronized修饰的,JVM会把相关的append操作合并成一个synchronized块。
// 粗化前
synchronized (obj) {
System.out.println("111");
}
synchronized (obj) {
System.out.println("222");
}
synchronized (obj) {
System.out.println("333");
}
// 粗化后
synchronized (obj) {
System.out.println("111");
System.out.println("222");
System.out.println("333");
}
锁消除
在使用synchronized时,当我们同步锁住的对象在方法体内时(一个栈帧)多线程执行不会有任何影响,JVM会消除当前方法的synchronized。
如果发现某个对象只能从一个线程可访问,那么在这个对象上的操作可以不需要同步。
说明了逃逸分析把锁消除了,并在性能上得到了很大的提升。这里说明一下Java的逃逸分析是方法级别的,因为JIT的即时编译是方法级别。
Object o = new Object();
// 消除前
synchronized (o) {
System.out.println("123");
}
// 消除后
System.out.println("123");
逃逸分析
这个简单来说就是把对象分解成一个个基本类型,并且内存分配不再是分配在堆上,而是分配在栈上。这样的好处有,一、减少内存使用,因为不用生成对象头。 二、程序内存回收效率高,并且GC频率也会减少,总的来说和上面优点一的效果差不多。
/**
* 逃逸分析开启之后 -XX:+DoEscapeAnalysis(JDK1.8默认开启
)
* jps -l
* jmap -histo id
* 可以看到对象并没有被创建一千次
*/
public static void main(String[] args){
for(int i = 0; i < 5_000_000; i++){
createObject();
}
}
public static void createObject(){
new Object();
}
这说明了JVM在逃逸分析之后,将对象分配在了方法createObject()方法栈上。方法栈上的对象在方法执行完之后,栈桢弹出,对象就会自动回收。这样的话就不需要等内存满时再触发内存回收。这样的好处是程序内存回收效率高,并且GC频率也会减少,程序的性能就提高了。











网友评论