一、synchronized 介绍
synchronized 是 JVM 内置锁,通过内部对象 Monitor (监视器锁)来实现,基于进入与退出 monitor 对象来实现方法与代码块的同步。
监视器锁的实现最终依赖操作系统的 Mutex lock(互斥锁)来实现。
1.1 monitorenter
每个对象有一个监视器锁(monitor)。当 monitor 被占用时就会处于锁定状态,线程执行 monitorenter 指令时尝试获取 monitor 的所有权,过程如下:
- 如果 monitor 的进入线程数为 0,则该线程进入 monitor,然后将进入数设置为 1,该线程即为 monitor 的所有者。
- 如果线程已经占有该 monitor,只是重新进入,则进入 monitor 的进入数加 1。
- 如果其他线程已经占用了 monitor,则该线程进入阻塞状态,直到 monitor 的进入数为 0,再重新尝试获取 monitor 的所有权。
1.2 monitorexit
执行 monitorexit 必须是 objectref 所对应的 monitor 的所有者。
指令执行时,monitor 的进入数减 1,如果减 1 后进入数为 0,那线程退出 monitor,不再是这个 monitor 的所有者。其他被这个 monitor 阻塞的线程可以尝试去获取这个 monitor 的所有权。
二、synchronized 加锁方式
synchronized 主要有 3 种加锁方式
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
- 静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
2.1 修饰实例方法
public class MyClass {
public synchronized void instanceMethod() {
// 这里是同步代码块
}
}
作用于当前实例加锁,进入同步代码前要获得当前实例的锁。
2.2 静态方法
public class MyClass {
public static synchronized void staticMethod() {
// 这里是同步代码块
}
}
// 或者
public class ClassName {
public void method() {
synchronized(ClassName.class) {
// 这里是同步代码块
}
}
}
作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
2.3 修饰代码块
public class TestClass{
private static Object object;
public void test(){
synchronized(object){
//这里是同步代码块
}
}
}
// 或者
public void test(){
synchronized(this){
//这里是同步代码块
}
}
指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
三、Java 对象组成
3.1 Java 对象由三部分组成
- 对象头:比较复杂,synchronized 加的锁,就保存在这里面的某些数据位。对象头大小是固定的。
- 实例数据:就是对象的业务数据。
- 对齐填充位:64 位 jvm,默认需要对象大小必须8byte(字节)的整数倍,所以有时候需要对齐填充位。
3.2 Mark Word
锁的不同状态,就是存在对象头中的Mark Word区域中。
下图是32位系统的Mark Word的区域具体分布:
64位 Mark Word
四、锁的升级过程
synchronized 锁有如下 4 种状态:
- 无锁,不锁住资源,多个线程只有一个能修改资源成功,其他线程会重试。
- 偏向锁,同一个线程获取同步资源时,没有别人竞争时,去掉所有同步操作,相当于没锁。
- 轻量级锁,多个线程抢夺同步资源时,没有获得锁的线程使用 CAS 自旋等待锁的释放。
- 重量级锁,多个线程抢夺同步资源时,使用操作系统的互斥量进行同步,没有获得锁的线程阻塞等待唤醒。
锁的升级过程:无锁-》偏向锁-》轻量级锁-》重量级锁。注意,升级并不一定是一级级升的,有可能跨级别,比如由无锁状态,直接升级为轻量级锁。
五、总结
synchronized 在 jdk 1.6 版本进行了优化,性能有了巨大提升,基本上和 java 锁性能没有什么差异,所以在生产环境中,synchronized 能满足的场景,尽量使用 synchronized,简单方便。
优化的关键,是使用了轻量级锁,使用 CAS,还有自适应机制,避免了向底层操作系统申请互斥量,避免了用户态和内核态的切换,也就是在一定程度上避免了线程上下文的切换,暂时不进入重量级锁的状态。
synchronized关键字是并发编程不可或缺的部分,Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
Synchronized是Java中解决并发问题的一种最常用最简单的方法 ,他可以确保线程互斥的访问同步代码