synchronized内置锁是一种对象锁(锁的是对象而非引用),作用粒度是对象,可以用来实现对临界资源的同步互斥访问,是可重入的。
加锁的方式:
1、同步实例方法,锁是当前实例对象
2、同步类方法,锁是当前类对象
3、同步代码块,锁是括号里面的对象
synchronized底层原理
synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁)实现,它是一个重量级锁性能较低。当然,JVM内置锁在1.5之后版本做了重大的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销,内置锁的并发性能已经基本与JUC的Lock持平。
synchronized关键字被编译成字节码后会被翻译成 monitorenter 和 monitorexit 两条指令分别在同步块逻辑代码的起始位置与结束位置。
每个同步对象都有一个自己的Monitor(监视器锁),加锁过程如下图所示:
Monitor监视器锁
任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。
synchronized在JVM里的实现都是基于进入和退出Monitor对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的MonitorEnter和MonitorExit指令来实现。
- monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权。
过程如下:
- 如果 monitor的进入数为0,则该线程进入 monitor,然后将进入数设置为1,该线程即为 monitor的所有者;
- 如果线程已经占有该 monitor,只是重新进入,则进入 monitor的进入数加1;
如果其他线程已经占用了 monitor,则该线程进入阻塞状态,直到 monitor的进入数为0,再重新尝试获取 monitor的所有权;
- monitorexit:执行 monitorexit的线程必须是 objectref 所对应的 monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出 monitor,不再是这个 monitor的所有者。其他被这个 monitor阻塞的线程可以尝试去获取这个 monitor的所有权。
通过上面两段描述,我们应该能很清楚的看出 synchronized的实现原理,synchronized的语义底层是通过一个 monitor的对象来完成,其实 wait/notify等方法也依赖于 monitor对象,这就是为什么只有在同步的块或者方法中才能调用 wait/notify等方法,否则会抛出 java.lang.IllegalMonitorStateException的异常的原因。
对象头中的锁标志(MarkWord前四个字节)
synchronized锁实现与升级过程
- 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁;
- 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1;
- 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁;
- 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁;
- 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁;
- 如果自旋成功则依然处于轻量级状态;
-
如果自旋失败,则升级为重量级锁。
补充内容一:
线程调度图(注:wait()、notify()、notifyAll()只能作用于synchronized同步块中)