以前的文章中介绍了
JAVA
中一些并发锁使用方法以及里面的介绍。同时以后还介绍了字节码的操做码,让你们先了解下里面的指令,我这里也是从表面中去讲解下锁底层操做码的实现。java
package com.montos.detail;
public class SynchronizedDemo {
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
demo.demo();
}
public void demo() {
synchronized (this) {
System.out.println("this is demo");
}
}
}
复制代码
对其反编译:数组
public class com.montos.detail.SynchronizedDemo {
public com.montos.detail.SynchronizedDemo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/montos/detail/SynchronizedDemo
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method demo:()V
12: return
public void demo();
Code:
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #6 // String this is demo
9: invokevirtual #7 // 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
Exception table:
from to target type
4 14 17 any
17 20 17 any
}
复制代码
查看上面反编译的结果,咱们能够看到反编译里面是存在monitorenter
以及monitorexit
的操做码,这两个操做码的做用就是:并发
monitorenter
:每一个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的全部权,过程以下:monitorexit
:执行monitorexit的线程必须是objectref所对应的monitor的全部者。指令执行时,monitor的进入数减1,若是减1后进入数为0,那线程退出monitor,再也不是这个monitor的全部者。其余被这个monitor阻塞的线程能够尝试去获取这个 monitor 的全部权。从而达到线程之间的串行执行,同时我能够看到里面有两次monitorexit
操做码:第1次为同步正常退出释放锁;第2次为发生异步退出释放锁;这上面锁住的就是this。异步
public class SynchronizedDemo {
public synchronized void method() {
System.out.println("this is demo");
}
}
复制代码
反编译:布局
public com.montos.detail.SynchronizedDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public synchronized void method();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String this is demo
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
复制代码
经过上面反编译,咱们发现没有以前的两个操做码了,多出来的是有标识ACC_SYNCHRONIZED
,这里其实也是经过上面两个操做码完成的。这个方法也只是比普通的方法在常量池中多了ACC_SYNCHRONIZED
字段。性能
当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED
访问标志是否被设置,若是设置了,执行线程将先获取monitor,获取成功以后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其余任何线程都没法再得到同一个monitor对象。this
上面的两种操做本质上没有区别,只是方法的同步是一种隐式方式操做的,两个指令的执行是JVM经过调用操做系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待从新调度,会致使“用户态和内核态”两个态之间来回切换,对性能有较大影响。spa
对象在内存中布局主要有:对象头,实例数据以及对齐填充。操作系统
Java
对象头通常占有2个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit,在64位虚拟机中,1个机器码是8个字节,也就是64bit),可是 若是对象是数组类型,则须要3个机器码,由于JVM虚拟机能够经过Java对象的元数据信息肯定Java对象的大小,可是没法从数组的元数据来确认数组的大小,因此用一块来记录数组长度。
Synchronized
用的锁就是存在Java
对象头里的,那么什么是Java
对象头呢?Hotspot虚拟机的对象头主要包括两部分数据:Mark Word
(标记字段)、Class Pointer
(类型指针)。其中Class Pointer
是对象指向它的类元数据的指针,虚拟机经过这个指针来肯定这个对象是哪一个类的实例,Mark Word
用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键。线程
这里面咱们主要注意的是Mark Word
这个存储结构。
每个
Java
对象建立出来就带了一把看不见的锁,它叫作内部锁或者Monitor锁
。Monitor
对象存在于每一个Java
对象的对象头Mark Word
中(存储的指针的指向),Synchronized
锁即是经过这种方式获取锁的,也是为何Java
中任意对象能够做为锁的缘由,同时notify/notifyAll/wait
等方法会使用到Monitor
锁对象,因此必须在同步代码块中使用。