1.引言
synchronized关键字是JDK提供的线程并发安全方案,本文将使用尽可能直白的描述,结合图表,对synchronized进行解析。
2.用法
2.1修饰静态方法
修饰静态方法时,锁对象为静态方法所在类的class对象,意味着,如果当前类的多个静态方法被synchronized关键字修饰,则这些方法是同步执行的,同一时刻只能有一个线程执行该类的静态方法,并且,若在其他代码块中对该类的class对象进行加锁,该代码块也会与静态方法同步,示例代码如下。
package share.synch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 静态方法/代码块
*
* @author shanpao
*/
public class SyncStatic {
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 1,
TimeUnit.SECONDS, new LinkedBlockingDeque<>(), r -> new Thread(r));
public static void main(String[] args) {
executor.submit(SyncStatic::sayHello);
executor.submit(SyncStatic::sayBye);
executor.submit(SyncStatic::sayYes);
SyncStatic syncStatic = new SyncStatic();
executor.submit(() -> syncStatic.sayNo());
executor.shutdown();
}
/**
* lock with class
*/
private static synchronized void sayHello() {
System.out.println("=== hello enter ===");
System.out.println(Thread.currentThread().getId() + " say hello!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=== hello exit ===");
}
/**
* lock with class
*/
private static synchronized void sayBye() {
System.out.println("--- bye enter ---");
System.out.println(Thread.currentThread().getId() + " say bye!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--- bye exit ---");
}
/**
* lock with class
*/
private static synchronized void sayYes() {
synchronized (SyncStatic.class) {
System.out.println("+++ yes enter +++");
System.out.println(Thread.currentThread().getId() + " say yes!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("+++ yes exit +++");
}
}
/**
* lock with this
*/
private synchronized void sayNo() {
System.out.println("+++ no enter +++");
System.out.println(Thread.currentThread().getId() + " say no!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("+++ no exit +++");
}
}
2.2 修饰实例方法
修饰实例方法时,锁定对象为当前类对象,即this指向的对象,即当前对象中所有的实例方法调用与其他基于当前对象的同步代码块执行都是同步的,实例代码如下。
package share.synch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 修饰实例方法
* @author shanpao
*/
public class SyncDynamic {
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 1,
TimeUnit.SECONDS, new LinkedBlockingDeque<>(), r -> new Thread(r));
public static void main(String[] args) {
SyncDynamic syncDynamic = new SyncDynamic();
executor.submit(() -> syncDynamic.sayHello());
executor.submit(() -> syncDynamic.sayBye());
executor.submit(() -> syncDynamic.sayYes());
}
private synchronized void sayHello() {
System.out.println("=== hello enter ===");
System.out.println(Thread.currentThread().getId() + " say hello!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=== hello exit ===");
}
private synchronized void sayBye() {
System.out.println("--- bye enter ---");
System.out.println(Thread.currentThread().getId() + " say bye!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--- bye exit ---");
}
private synchronized void sayYes() {
synchronized (this) {
System.out.println("+++ yes enter +++");
System.out.println(Thread.currentThread().getId() + " say yes!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("+++ yes exit +++");
}
}
}
2.3 修饰代码块
修饰代码块时,锁定对象为括号内的参数,可以为任意类型的对象,同步代码块使用的对象相同时,各代码块同步执行。此处需要注意单例对象的加锁,以及系统中缓存的对象,如Integer,Boolean对象等,实例代码如下。
package share.synch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 修饰代码块
*
* @author shanpao
*/
public class SyncBlock {
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 1,
TimeUnit.SECONDS, new LinkedBlockingDeque<>(), r -> new Thread(r));
public static void main(String[] args) {
SyncBlock syncBlock = new SyncBlock();
executor.submit(() -> syncBlock.sayHello());
executor.submit(() -> syncBlock.sayBye());
executor.submit(() -> syncBlock.sayYes());
executor.submit(() -> syncBlock.sayNo());
executor.submit(() -> syncBlock.sayNothing());
executor.shutdown();
}
private void sayHello() {
synchronized (this) {
System.out.println("=== hello enter ===");
System.out.println(Thread.currentThread().getId() + " say hello!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=== hello exit ===");
}
}
private void sayBye() {
synchronized (this) {
System.out.println("--- bye enter ---");
System.out.println(Thread.currentThread().getId() + " say bye!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--- bye exit ---");
}
}
private void sayYes() {
synchronized (this) {
System.out.println("+++ yes enter +++");
System.out.println(Thread.currentThread().getId() + " say yes!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("+++ yes exit +++");
}
}
private void sayNo() {
Object obj = new Object();
synchronized (obj) {
System.out.println("+++ no enter +++");
System.out.println(Thread.currentThread().getId() + " say no!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("+++ no exit +++");
}
}
private void sayNothing() {
synchronized (new Integer(1)) {
System.out.println("+++ nothing enter +++");
System.out.println(Thread.currentThread().getId() + " say nothing!");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("+++ nothing exit +++");
}
}
}
3.实现
3.1 虚拟机指令
以同步代码块为例,反编译查看虚拟机指令。
package share.synch;
/**
* 同步代码块虚拟机指令
*
* @author shanpao
*/
public class SyncJavac {
public static void main(String[] args) {
synchronized (SyncJavac.class) {
String a = "a";
}
}
}
javap -v SyncJavac
Compiled from "SyncJavac.java"
public class share.synch.SyncJavac {
public share.synch.SyncJavac();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // class share/synch/SyncJavac
2: dup
3: astore_1
4: monitorenter
5: ldc #3 // String a
7: astore_2
8: aload_1
9: monitorexit
10: goto 18
13: astore_3
14: aload_1
15: monitorexit
16: aload_3
17: athrow
18: return
Exception table:
from to target type
5 10 13 any
13 16 13 any
}
由反汇编得到的Java指令集可以看出,同步代码块中的逻辑与monitorenter monitorexit两条指令之间逻辑对应,这两条指令表示程序进入临界区/退出临界区,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,该对象将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。
3.2 Mark Word
synchronized存的锁是在对象头里的,对象头存储的数据内容如下表所示。
![](https://i-blog.csdnimg.cn/blog_migrate/95ccc29ef6c10b153c7fc1ec7900cbd3.jpeg)
Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构如表所示
![](https://i-blog.csdnimg.cn/blog_migrate/9aae261dbdf265fe399c4159fa818879.jpeg)
在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据
![](https://i-blog.csdnimg.cn/blog_migrate/81ab6edaa82f255d630891e5d75a6d72.jpeg)
3.3 锁状态
Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。
3.3.1 偏向锁
Biased locking is an optimization technique used in the HotSpot Virtual Machine to reduce the overhead of uncontended locking. It aims to avoid executing a compare-and-swap atomic operation when acquiring a monitor by assuming that a monitor remains owned by a given thread until a different thread tries to acquire it. The initial lock of the monitor biases the monitor towards that thread, avoiding the need for atomic instructions in subsequent synchronized operations on the same object. When many threads perform many synchronized operations on objects used in a single-threaded fashion, biasing the locks has historically led to significant performance improvements over regular locking techniques. OPEN JDK
偏向锁定是HotSpot虚拟机中使用的一种优化技术,可以减少无竞争锁定的开销。它的目的是通过假定监视器一直归给定线程拥有,直到另一个线程尝试获取它为止,从而避免在获取监视器时执行比较交换原子操作。监视器的初始锁定将监视器偏向该线程,从而避免了对同一对象进行后续同步操作时需要原子指令。当许多线程对以单线程方式使用的对象执行许多同步操作时,数据显示,对锁施加偏向已导致与常规锁定技术相比,性能得到显着改善。
偏向锁更像一个标记位,对持有偏向锁的线程而言,只要mark word中的线程指向自己,就不再需要加锁操作,当其他线程尝试去获取偏向锁的时候,发现偏向锁指向了其他线程,偏向锁就会升级为轻量级锁。
加锁:测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
释放锁:偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,(如果线程不处于活动状态/已退出同步代码块,则将对象头设置成无锁状态,撤销偏向锁;如果线程仍然活着且未退出同步代码块,此时锁会膨胀为轻量级锁)最后唤醒暂停的线程。
3.3.2 轻量级锁
线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced MarkWord。
加锁:线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁;
释放锁:使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。
3.3.3 重量级锁
当锁对象被设置为重量级锁状态,其他来获取锁的线程会被阻塞,整体synchronized加锁的流程如下图(来自网络,侵删)。
![](https://i-blog.csdnimg.cn/blog_migrate/a5d9757b2550b33cb43b23c383011206.png)
4.总结
JVM团队在尽可能减少并发带来的线程上下文切换,估引入了偏向锁和轻量级锁,通俗得讲,在没有任何线程冲突时,采用不加锁策略,只是检查锁对象是否当前线程的偏向对象,当发生冲突时,升级为轻量级锁,通过自旋检查的方式避免冲突,当自旋也无法规避冲突时,锁升级为重量级锁,多线程互斥访问。