synchronized
参考:https://www.cnblogs.com/javaminer/p/3889023.html
# 1 synchronized介绍
- 在java语言中存在两种内建的synchronized语法:1、synchronized语句;2、synchronized方法。
1.synchronized语句 当Java源代码被javac编译成bytecode的时候,会在同步块的入口位置和退出位置分别插入monitorenter和monitorexit字节码指令。 // 进入 synchronized 区块,使用 lock 作为锁对象 synchronized (lock) { // 这里的代码只能由一个线程执行 // ... } 2.synchronized方法 而synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。 // 使用 synchronized 修饰方法,锁对象是当前实例(this) public synchronized void synchronizedMethod() { // 这里的代码只能由一个线程执行 // ... } // 静态 synchronized 方法,锁对象是类对象 public static synchronized void staticSynchronizedMethod() { // 这里的代码只能由一个线程执行 // ... }
简单总结一下:
synchronized
同步语句块的实现使用的是 monitorenter
和 monitorexit
指令,其中 monitorenter
指令指向同步代码块的开始位置,monitorexit
指令则指明同步代码块的结束位置。
synchronized
修饰的方法并没有 monitorenter
指令和 monitorexit
指令,取得代之的确实是 ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法。
不过两者的本质都是对对象监视器 monitor 的获取。
# 二、JVM中锁的优化
也就是说jdk自带的锁很笨重,每次都要去看看别人拿了这个锁没有,没人用我再用,这样会有很大的开销,后来jkd1.6优化了,里面有个CAS原子操作来判断别人是否正在用这个方法,CAS(compare-and-swap)原子指令返回成功或失败:尝试用一个变量值与期望值比较,如果相等,这个变量的值更新,CAS是乐观锁定方式,允许多个线程同时尝试更新一个共享变量,但是只有一个会成功。
简单来说在JVM中monitorenter和monitorexit字节码依赖于底层的操作系统的Mutex Lock来实现的,但是由于使用Mutex Lock需要将当前线程挂起并从用户态切换到内核态来执行,
切换用户态到内核态的目的是:
允许操作系统执行调度,决定哪个线程应该被唤醒以获得锁,以及哪个线程应该被挂起。
允许操作系统执行阻塞操作,将等待锁的线程挂起,直到锁可用。
确保在多线程环境中的线程安全性。
这种切换的代价是非常昂贵的;然而在现实中的大部分情况下,同步方法是运行在单线程环境(无锁竞争环境)如果每次都调用Mutex Lock那么将严重的影响程序的性能。不过在jdk1.6中对锁的实现引入了大量的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销。
**锁粗化(Lock Coarsening):**也就是减少不必要的紧连在一起的unlock,lock操作,将多个连续的锁扩展成一个范围更大的锁。
**锁消除(Lock Elimination):**通过运行时JIT编译器的逃逸分析来消除一些没有在当前同步块以外被其他线程共享的数据的锁保护,通过逃逸分析也可以在线程本地Stack上进行对象空间的分配(同时还可以减少Heap上的垃圾收集开销)。
**轻量级锁(Lightweight Locking):**这种锁实现的背后基于这样一种假设,即在真实的情况下我们程序中的大部分同步代码一般都处于无锁竞争状态(即单线程执行环境),在无锁竞争的情况下完全可以避免调用操作系统层面的重量级互斥锁,取而代之的是在monitorenter和monitorexit中只需要依靠一条CAS原子指令就可以完成锁的获取及释放。当存在锁竞争的情况下,执行CAS指令失败的线程将调用操作系统互斥锁进入到阻塞状态,当锁被释放的时候被唤醒(具体处理步骤下面详细讨论)。
**偏向锁(Biased Locking):**是为了在无锁竞争的情况下避免在锁获取过程中执行不必要的CAS原子指令,因为CAS原子指令虽然相对于重量级锁来说开销比较小但还是存在非常可观的本地延迟(可参考这篇文章 (opens new window))。
**适应性自旋(Adaptive Spinning):**当线程在获取轻量级锁的过程中执行CAS操作失败时,在进入与monitor相关联的操作系统重量级锁(mutex semaphore)前会进入忙等待(Spinning)然后再次尝试,当尝试一定的次数后如果仍然没有成功则调用与该monitor关联的semaphore(即互斥锁)进入到阻塞状态