synchronized用法参考上一篇文章,多线程与高并发(2)——synchronized用法详解,里面也详细讲了原子性、可见性和互斥性。本文主要总结synchronized的原理。 synchronized的同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题。
一、反编译
我们先看以下代码:
// 对象锁:形式1(方法锁)
public synchronized void method1() {
System.out.println("我是对象锁也是方法锁");
try {
System.out.println("我要睡500ms");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 对象锁:形式2(代码块形式)
public void method2() {
synchronized (this) {
try {
System.out.println("我是对象锁,我要睡500ms");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 类锁:形式1 :锁静态方法
public static synchronized void method3() {
System.out.println("我是类锁1");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void method4() {
synchronized (DemoSync.class) {
System.out.println("我是类锁种类2");
}
这是在上一篇文章中总结的synchronized用法,方法12锁的是对象,方法3锁的是整个类(或者说这个类的Class对象)。这里我们用javap -v命令进行class文件的反编译,结果分别如下。
1、method1 2、method2 3、method3 4、method4
二、monitorenter和monitorexit
1、monitorenter 源码中这么解释:
Each object is associated with a monitor. A monitor is locked if and only if it has an
owner. The thread that executes monitorenter attempts to gain ownership of the
monitor associated with objectref, as follows:
? If the entry count of the monitor associated with objectref is zero, the thread
enters the monitor and sets its entry count to one. The thread is then the owner of
the monitor.
? If the thread already owns the monitor associated with objectref, it reenters the
monitor, incrementing its entry count.
? If another thread already owns the monitor associated with objectref, the thread
blocks until the monitor's entry count is zero, then tries again to gain ownership.
翻译过来就是: 每个对象有一个监视器(monitor),或者说叫管程。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下: (1)如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。 (2)如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。 (3)如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。 2、monitorexit
The thread that executes monitorexit must be the owner of the monitor associated with
the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as
a result the value of the entry count is zero, the thread exits the monitor and is no
longer its owner. Other threads that are blocking to enter the monitor are allowed to
attempt to do so.
翻译过来就是: 执行monitorexit的线程必须是该对象所对应的monitor的所有者。 monitorexit指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。 可以看我们多线程系列的第一篇文章,多线程与高并发(1)——线程的基本知识(实现,常用方法,状态),里面有讲到wait()方法必须在同步关键字修饰的方法中才能调用。调用notify或者notifyAll方法并不释放锁,而是让他参与锁的竞争中去。
三、ACC_SYNCHRONIZED
方法1和3的同步并没有通过指令 monitorenter 和 monitorexit 来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了 ACC_SYNCHRONIZED,ACC_STATIC 标示符。JVM就是根据ACC_SYNCHRONIZED来实现方法的同步的:
当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,
执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后(无论是正常或者非正常)
再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。
如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那么这个同步方法所持有的管程将在异常抛到同步方法之外时自动释放。 两种同步方式本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。
四、总结
对于method1234来说,本质上都是对象去竞争monitor,谁争到了谁就去执行。
|