Synchronized
我们知道 Synchronized 是 Java 中解决并发问题的一种最常用的方法, 也是最简单的一种方法. 被也被称为内置锁.
Synchronized 的作用主要有三个:
- 确保线程互斥的访问同步代码
- 保证共享变量的修改能够及时可见
- 有效解决重排序问题。
从语法上讲, Synchronized 总共有三种用法:
- 修饰普通方法, 锁是当前实例对象.
- 修饰静态方法, 锁是当前类的
class对象. - 修饰代码块, 锁是括号中的对象.
关于使用方式, 这里就不再进行一一描述了. 我们直接进入正题, 看 Synchronized 的底层实现原理是什么.
1. Synchronized 原理
首先, 我们先来看一段代码, 使用了同步代码块和同步方法, 通过使用 javap 工具查看生成的 class 文件信息来分析 synchronized 关键字的实现细节.
对代码进行反编译后的结果如下
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 的简单学习 这篇文章的朋友, 再来看这里, 我相信应该会很容易理解.
- 点赞
- 收藏
- 关注作者
评论(0)