在Java并发编程中,synchronized
关键字是实现线程同步的重要工具。它能够确保多个线程在访问共享资源时的线程安全性。随着Java版本的更新,synchronized
的底层实现也在不断优化,尤其是引入了锁升级机制,显著提高了性能。本文将详细介绍synchronized
的使用方法、底层原理以及锁升级机制。
目录
1.1 什么是synchronized
?
synchronized
是Java中的关键字,用于实现线程同步。它可以修饰方法或代码块,确保同一时刻只有一个线程能够执行被修饰的代码。
1.2 synchronized
的三种使用方式
1.2.1 修饰实例方法
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
-
锁对象是当前实例(
this
)。 -
同一实例的多个线程会竞争同一把锁。
1.2.2 修饰静态方法
public class Counter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static int getCount() {
return count;
}
}
-
锁对象是当前类的
Class
对象(Counter.class
)。 -
所有实例的线程都会竞争同一把锁。
1.2.3 修饰代码块
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
}
-
锁对象是自定义的对象(
lock
)。 -
可以更灵活地控制锁的范围。
2. synchronized
的底层原理
2.1 对象头与Monitor
在JVM中,每个对象都有一个对象头,其中包含以下信息:
-
Mark Word:存储对象的哈希码、锁状态等。
-
Klass Pointer:指向对象的类元数据。
synchronized
的实现依赖于对象头中的Mark Word
和一个称为Monitor
(监视器)的机制。每个Java对象都与一个Monitor
关联,Monitor
是线程同步的核心。
2.2 Monitor的工作原理
-
当线程进入
synchronized
代码块时,它会尝试获取对象的Monitor
。 -
如果
Monitor
未被占用,线程将成功获取锁并进入临界区。 -
如果
Monitor
已被其他线程占用,当前线程会被阻塞,进入EntryList
等待。 -
当持有锁的线程释放锁时,会唤醒
EntryList
中的线程重新竞争锁。
3. 锁升级机制
在Java 6之前,synchronized
的实现是完全基于重量级锁(依赖于操作系统的互斥锁),性能较差。为了优化性能,Java 6引入了锁升级机制,包括偏向锁、轻量级锁和重量级锁。
3.1 锁的四种状态
-
无锁状态:对象未被任何线程锁定。
-
偏向锁:适用于只有一个线程访问同步代码块的场景。
-
轻量级锁:适用于多个线程交替访问同步代码块的场景。
-
重量级锁:适用于多个线程竞争同一把锁的场景。
3.2 锁升级的过程
3.2.1 偏向锁
-
适用场景:只有一个线程访问同步代码块。
-
实现原理:
-
在对象头中记录线程ID。
-
如果同一线程再次访问同步代码块,无需加锁,直接进入。
-
-
优点:减少锁操作的开销。
3.2.2 轻量级锁
-
适用场景:多个线程交替访问同步代码块,但没有竞争。
-
实现原理:
-
使用CAS(Compare-And-Swap)操作尝试获取锁。
-
如果成功,对象头中存储指向线程栈中锁记录的指针。
-
如果失败,说明存在竞争,升级为重量级锁。
-
-
优点:避免线程阻塞,减少上下文切换。
3.2.3 重量级锁
-
适用场景:多个线程竞争同一把锁。
-
实现原理:
-
使用操作系统的互斥锁(Mutex)实现。
-
未获取锁的线程会被阻塞,进入
EntryList
等待。
-
-
缺点:性能开销较大,涉及用户态和内核态的切换。
3.2.4 锁升级的流程图
无锁状态 -> 偏向锁 -> 轻量级锁 -> 重量级锁
4. synchronized
的性能优化
4.1 减少锁的粒度
-
尽量缩小同步代码块的范围,减少锁的持有时间。
-
例如,使用细粒度锁(如
ConcurrentHashMap
中的分段锁)。
4.2 避免锁的嵌套
-
锁嵌套容易导致死锁,应尽量避免。
4.3 使用java.util.concurrent
工具包
-
在高并发场景下,可以使用
ReentrantLock
、ReadWriteLock
等更灵活的锁机制。
5. 示例代码
以下是一个使用synchronized
的完整示例,展示了锁的竞争与释放:
public class SynchronizedDemo {
private static int count = 0;
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
synchronized (lock) {
count++;
}
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + count); // 输出 2000
}
}
6. 总结
synchronized
是Java中实现线程同步的重要工具,通过锁升级机制(偏向锁、轻量级锁、重量级锁),它在保证线程安全的同时,显著提高了性能。在实际开发中,合理使用synchronized
并结合其他并发工具,可以有效提升程序的并发性能。