我们知道 Synchronized
是 Java
中解决并发问题的一种最常用的方法, 也是最简单的一种方法. 被也被称为内置锁.
Synchronized
的作用主要有三个:
- 确保线程互斥的访问同步代码
- 保证共享变量的修改能够及时可见
- 有效解决重排序问题。
从语法上讲, Synchronized
总共有三种用法:
- 修饰普通方法, 锁是当前实例对象.
- 修饰静态方法, 锁是当前类的
class
对象. - 修饰代码块, 锁是括号中的对象.
关于使用方式, 这里就不再进行一一描述了. 我们直接进入正题, 看 Synchronized
的底层实现原理是什么.
1. Synchronized 原理
首先, 我们先来看一段代码, 使用了同步代码块和同步方法, 通过使用 javap
工具查看生成的 class
文件信息来分析 synchronized
关键字的实现细节.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6O3iT05F-1615815512334)(//upload-images.jianshu.io/upload_images/7115372-3a18801ac85d8535.png?imageMogr2/auto-orient/strip|imageView2/2/w/592/format/webp)]
对代码进行反编译后的结果如下
public static void main(java.lang.String[]) throws java.lang.Exception;
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic
3: dup
4: astore_1
5: monitorenter //---------------------------------------------1.
6: aload_1
7: monitorexit //---------------------------------------------2.
8: goto 16
11: astore_2
12: aload_1
13: monitorexit //---------------------------------------------3.
14: aload_2
15: athrow
16: return
...
public static synchronized void test();
descriptor: ()V
flags: (0x0029) ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED //---------------------------------------------4.
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 21: 0
从生产的 class
信息中, 可以清楚的看到两部分内容
- 同步代码块中使用了
monitorenter
与monitorexit
指令. - 同步方法中依靠方法修饰符
flags
上的ACC_SYNCHRONIZED
实现.
先看反编译出 main
方法中标记的 1 与 2. monitorenter / monitorexit
关于这两条指令的作用, 参考 JVM
中对他们的描述如下:
monitorenter
每个对象有一个监视器锁 monitor
, 当 monitor
被占用时就会处于锁定状态, 线程执行 monitorenter
指令时尝试获取 monitor
的所有权, 过程如下
- 如果
monitor
的进入数为 0 , 则该线程进入monitor
, 然后将进入数设置为 1, 该线程即为monitor
的拥有者. - 如果线程已经占有该
monitor
, 只是重新进入, 则进入monitor
的进入数加 1. - 如果其他线程已经占用了
monitor
, 则该线程进入阻塞状态, 直到monitor
的进入数为 0, 再尝试获取monitor
的所有权.
monitorexit
执行 monitorexit
的线程必须是对应 monitor
的所有者. 执行指令时, monitor
的进入数减 1. 如果减 1 后进入数为 0, 则线程退出 monitor
. 不再是这个 monitor
的所有者. 其他被这个 monitor
阻塞的线程可以尝试去获取这个 monitor
的所有权.
monitorenter
指令是在编译后插入到同步代码块开始的位置, 而monitorexit
是插入到方法的结束处和异常处. 这也就是为什么在 3 处会单独有一个monitorexit
了.
ACC_SYNCHRONIZED
当方法调用时, 调用指令将检查方法的 ACC_SYNCHRONIZED
访问标志是否被设置, 如果设置了, 执行线程将先获取 monitor
, 获取成功之后才能执行方法体. 方法执行完后再释放 monitor
, 在方法执行期间, 其他任何线程都无法再获得同一个 monitor
对象.
其实这个和上面 monitorenter
与 monitorexit
本质上没有区别, 只是方法的同步是一种隐式的方式来实现的, 无需通过字节码来完成.
看完这些, 是不是觉得有点和 AQS 中的 state
相似? 如果看完了 从 LockSupport 到 AQS 的简单学习 这篇文章的朋友, 再来看这里, 我相信应该会很容易理解.
这里既然说到了监视器锁 monitor
, 我们一起来看这