synchronized 简介
在并发编程中,当多个线程竞争共享资源时,为了实现对共享资源的访问互斥,通常使用 synchronized 关键字进行加锁,在 Java 中,每一个对象都可以作为锁
synchronized 的三种使用方式
修饰代码块
static int count = 0;
static Object lock = new Object();
synchronized (lock) {
count++;
}
synchronized (lock) {
count--;
}
创建 Object 对象 lock 作为锁,对于 count 的自增自减操作,都使用 synchronized 对代码块加锁,当一个线程对 count 进行自增或自减操作时,另外的线程由于获取不到 lock 锁而被阻塞,直到执行完毕,释放锁后,另外的线程才有机会获取锁,保证了临界区内代码的原子性
修饰普通方法
使用 synchronized 修饰普通方法
public class SynchronizedTest {
public synchronized void test() {
}
}
等价于
public class SynchronizedTest {
public void test() {
synchronized (this) {
}
}
}
此时锁是当前调用方法的对象
修饰静态方法
使用 synchronized 修饰静态方法
public class SynchronizedTest {
public synchronized static void test() {
}
}
等价于
public class SynchronizedTest {
public static void test() {
synchronized (SynchronizedTest.class) {
}
}
}
此时锁是当前类的 class 对象
底层原理详解
Java 对象头
Java 的对象头由 Mark Word 、Klass Pointer 、Array Length(若为数组对象)组成
Mark Word :保存了对象和锁的信息
Klass Pointer :保存了一个指向类对象的指针
Array Length :保存了数组长度
对于 32 位的 JVM ,Mark Word 的长度是 32 bit ,组成如下
锁状态 | 25bit | 4bit | 1bit | 2bit | |
23bit | 2bit | 是否可偏向 | 锁标志位 | ||
无 | 对象的 HashCode | 分代年龄 | 0 | 01 | |
偏向锁 | 线程 ID | Epoch | 分代年龄 | 1 | 01 |
轻量级锁 | 指向栈中锁记录的指针 | 00 | |||
重量级锁 | 指向重量级锁的指针 | 10 | |||
GC 标记 | 空 | 11 |
Monitor
又称监视器或管程,每一个对象都有一个对应的 Monitor
Monitor 结构如图所示
synchronized 原理(重量级锁)
当一个线程通过 synchronized 给一个对象上锁,访问临界区时
static Object lock = new Object();
synchronized (lock) {
count++;
}
该 lock 对象的对象头中的 Mark Word 的前 30 bit 就被设置成指向该对象的 Monitor 的指针,且该 Monitor 的 Owner 设置为该线程,Owner 只能有一个,此时如果有其他线程试图获取锁,则会被阻塞,进入 EntryList 中,当线程执行完毕后释放 Monitor ,并唤醒 EntryList 中阻塞的线程,通过 CPU 调度,让其中一个线程运行
对于 lock 对象的对象头中的 Mark Word 的 HashCode 等信息,会暂存在 Monitor 中,便于解锁后恢复