Synchronized 概述
1. 初识
synchronized 是 Java 中的关键字,是一种 同步锁 ,可重入锁,悲观锁。它修饰的对象有以下几种:
具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的 Class 对象。
对于同步方法块,锁是 synchonized 括号里配置的对象。
虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法定义的一部分,因此,synchronized
关键字不能被继承。 如果在父类中的某个方法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这
个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上 synchronized
关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法本身不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待持有锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
- 获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
- 线程执行发生异常,此时 JVM 会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待 IO 或者其他原因(比如调用 sleep
方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 Lock 就可以办到。
2. synchronized 的底层字节码
synchronized 三种用法及其原理
2.1 同步代码块
通过反编译可以看到, 其实现使用的是 monitorenter 和 monitorexit 指令。
2.2 普通同步方法
调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置。如果设置了,执行线程会将先持有 monitor 然后再执行方法,最后在方法完成(正常和异常情况都算完成)时释放 monitor。
2.3 类同步方法
ACC_STATIC 和 ACC_SYNCHRONIZED 访问标志区分该方法是否是静态同步方法。
3. synchronized 锁的是什么
synchronized 用的锁是存在 Java 对象头里的。
3.1 什么是管程
管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。
这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。
3.2 monitor ObjectMonitor
在 HotSpot 虚拟机中, monitor 采用 ObjectMonitor 实现
ObjectMonitor.java ->> ObjectMonitor.cpp ->> ObjectMonitor.hpp
Java 中的每个对象天生都带着一个对象的监视器(所以 Java 中任何一个对象都可以成为一个锁)。
任何对象都有一个 monitor 与之关联,当且一个 monitor 被持有后,它将处于锁定状态。线程执行到 被 synchronized 修饰的方法时,将会 尝试获取对象头的 monitor 的所有权,即尝试获得对象的锁。