synchronized底层实现
synchronized时Java的内建锁,可以保证在多线程的情况下,每次有且仅有一个线程访问共享资源
同步的原理:monitor机制
同步代码块
class TestMonitor implements Runnable{
@Override
public void run() {
synchronized (this){
System.out.println("hello Java");
}
}
}
public class MonitorDemo {
public static void main(String[] args) {
TestMonitor testMonitor = new TestMonitor();
Thread thread = new Thread(testMonitor);
thread.start();
}
}
通过javap命令查看响应字节码文件
执行同步代码块后首先要执行monitorenter指令,退出时执行monitorexit指令。
使用内建锁(synchronized)进行同步,关键在于要获取指定锁对象,当线程获取monitor后才能继续向下执行,否则就只能等待。这个过程是互斥的,即同一时刻只有一个线程能够获取到对象monitor。
为什么会有两个monitorexit?
通常一个monitorenter指令会包含若干个monitorexit指令。原因在于jvm需要确保在正常执行路径以及异常执行路径上都要能够正确的解锁。(正确与否都要确保正确解锁)
同步方法:
可以看到字节码中包含ACC_SYNCHRONIZED这样的标志位,这个标记表示,在进入该方法时,JVM需要进行monitorenter操作。在退出该方法时,无论是正常还是异常,都要进行monitorexit操作
- 当执行monitorenter时,如果目标锁对象的monitor计数器为0,表示此对象没有被任何其他对象所持有。此时jvm会将该锁对象的持有线程设置为当前线程,并且将计数器加1
- 如果目标锁对象的monitor计数器不为0,判断锁对象的持有线程是否是当前线程,如果是再将计数器加1(锁的可重入性)
- 如果锁对象的持有线程不是当前线程,当前线程需要等待,直至持有线程释放锁
- 当执行monitirexit时,jvm会将锁对象的计数器-1.当计数器值减为0时,代表该锁对象已经被释放。
锁的可重入性:
class MyEnter implements Runnable{
@Override
public void run() {
test1();
}
public synchronized void test1(){
System.out.println(Thread.currentThread().getName()+" test1");
System.out.println("test over,test2 bagin");
test2();
}
public void test2(){
System.out.println(Thread.currentThread().getName()+" test2");
}
}