枯燥的Java并发 - synchronized

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 两条指令分别在同步块逻辑代码的起始位置与结束位置。

image.png

每个同步对象都有一个自己的Monitor(监视器锁),加锁过程如下图所示:
image.png

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前四个字节)

image.png

synchronized锁实现与升级过程

  1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁;
  2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1;
  3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁;
  4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁;
  5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁;
  6. 如果自旋成功则依然处于轻量级状态;
  7. 如果自旋失败,则升级为重量级锁。


    synchronized锁实现与升级过程.png
补充内容一:

线程调度图(注:wait()、notify()、notifyAll()只能作用于synchronized同步块中)


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。