synchronized 简介
synchronized
是 Java 中的一种同步机制,用于防止多个线程同时访问共享资源。它可以用于方法和代码块,确保同一时间只有一个线程可以执行被 synchronized
保护的代码,同时还可以保证共享变量的内存可见性。
Java中每一个对象都可以作为锁,这是synchronized
实现同步的基础。
synchronized 的特性
原子性:synchronized
保证了被保护的代码块在一个线程执行期间不会被其他线程中断。
可见性:synchronized
确保在锁释放前对共享变量的修改对其他线程可见。
有序性:synchronized
保证同一时刻只能有一个线程进行操作,也就保证了有序性,有效解决指令重排对程序的影响。
重入性:同一个线程可以多次获取同一把锁而不会被阻塞。
synchronized的使用
同步实例方法
1 | public class ThreadTest implements Runnable{ |
修饰静态方法
1 | public class ThreadTest { |
修饰代码块
1 | public class ThreadTest implements Runnable{ |
synchronized
修饰的是实例方法时,获得该实例对象的内置锁。synchronized
修饰的时静态方法时,获得类锁,即当前类的Class对象的内置锁。synchronized
修饰的是代码块时,根据括号中的对象或者类,获得相应的对象内置锁或者类锁。- 每个类都有一个类锁,类的每个对象都有一个内置锁,它们是互不干扰的,也就是说一个线程可以同时获得类锁和该类实例化对象的内置锁。
synchronized的实现原理
synchronized
的底层实现依赖于 JVM
中的对象头和 Monitor
对象。
对象头:每个 Java 对象都有一个对象头,包含了锁标志位和指向
Monitor
对象的指针。Monitor对象:
Monitor
是 JVM 内部的一个同步工具,用于管理线程的同步。
锁的状态
无锁状态:对象未被锁定。
偏向锁:锁偏向于第一个获取它的线程,降低了无竞争情况下的加锁和解锁成本。
轻量级锁:多个线程竞争锁时,锁会膨胀为轻量级锁。
重量级锁:竞争激烈时,锁会膨胀为重量级锁,使用操作系统的
Mutex
进行管理。
锁的强度会随着多线程竞争的激烈而逐渐升级。
synchronized的字节码实现
通过下面的测试代码来分析:
1
2
3
4
5
6
7
8
9
10
11public class SynchronizedDemo {
private int i=1;
public void test1(){
synchronized (this){
i++;
}
}
public synchronized void test2(){
i++;
}
}执行
javap -verbose SynchronizedDemo.class
反编译
其中test1()
方法的字节码如下:
1 | public void test1(); |
test1()
使用了synchronized
代码块,字节码中通过monitorenter
指令获取锁,通过monitorexit
指令释放锁。注意到其中只有1个monitorenter
但是有2个monitorexit
,因为synchronized
是JVM层面的锁,如果发生异常了JVM会自动帮我们释放锁,而Lock异常则需要我们手动捕获并释放。
关于这两条指令的作用,我们直接参考JVM规范中的描述:
monitorenter
1 | 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: |
monitorexit
1 | The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref. |
通过这两段描述,我们应该能很清楚的看出synchronized
的实现原理,synchronized
的语义底层是通过一个monitor
的对象来完成,其实wait()
/notify()
等方法也依赖于monitor
对象,这就是为什么只有在同步块或者方法中才能调用wait()
/notify()
等方法,否则会抛出java.lang.IllegalMonitorStateException
异常的原因。
test2()
方法的字节码如下:
1 | public synchronized void test2(); |
test2()
使用了synchronized
方法,虽然在字节码中并没有看到monitorenter
和monitorexit
指令,但在字节码常量池中多了ACC_SYNCHRONIZED
标志符,JVM就是根据该标示符来实现方法同步的。当方法调用时,调用指令会检查该标志符是否被设置,如果设置了,执行线程将先获取monitor
,获取成功之后才能执行方法体,方法执行完后再释放monitor
。在方法执行期间,其他任何线程都无法再获得同一个monitor
对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现。
总结
synchronized
是Java并发编程中最常用的用于保证线程安全的方式,使用起来非常简单和方便。但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用synchronized
关键字,另一方面也能够帮助我们更好的理解并发编程机制,有助于我们在不同的情况下选择更优的并发策略来完成任务。对平时遇到的各种并发问题,也能够从容的应对。