synchronized关键字
synchronized
关键字是 Java 中用于实现线程同步的核心机制之一,它用于确保多个线程对共享资源的访问是互斥的,从而避免线程安全问题。
# 1. synchronized 的基本概念
- 互斥锁:
synchronized
关键字可以用于方法或代码块,保证同一时间只有一个线程可以执行被保护的代码,从而实现对共享资源的互斥访问。 - 内置锁:每个 Java 对象都有一个内置的锁(Monitor),
synchronized
利用这个内置锁来控制线程的访问。
# 2. synchronized 的使用方式
synchronized
可以用来修饰方法和代码块,下面分别进行详细说明。
# 2.1 修饰实例方法
- 实例方法加锁:
public synchronized void increment() { count++; }
- 当一个线程调用
increment()
方法时,其他线程将无法调用该对象的任何其他synchronized
方法。 - 锁的对象是当前实例 (
this
),即同一实例对象的多个线程在调用同步方法时是互斥的。
- 当一个线程调用
# 2.2 修饰静态方法
- 静态方法加锁:
public static synchronized void print() { System.out.println("Static method"); }
- 锁的对象是类的
Class
对象 (ClassName.class
),即针对所有该类的实例共享同一把锁。 - 保证同一类的多个线程在调用
synchronized
静态方法时是互斥的。
- 锁的对象是类的
# 2.3 修饰代码块
- 同步代码块:
public void increment() { synchronized (this) { count++; } }
- 可以在方法内部使用
synchronized
关键字包裹部分代码,减少锁的粒度,从而提高程序的并发性。 - 可以对不同的对象加锁,比如
synchronized (lock)
,灵活控制同步的范围。
- 可以在方法内部使用
# 3. synchronized 的锁对象
synchronized
使用的锁对象可以是当前实例 (this
)、类的 Class
对象 (ClassName.class
)、或者是其他任意的对象。
- 对象锁:修饰实例方法或代码块,锁住的是当前对象,线程进入
synchronized
方法或代码块之前必须获得对象的锁。 - 类锁:修饰静态方法,锁住的是类的
Class
对象,保证该类的所有实例在访问同步静态方法时是互斥的。 - 自定义对象锁:通过同步代码块对某个特定对象加锁,可以灵活控制锁的粒度。例如:
private final Object lock = new Object(); public void performTask() { synchronized (lock) { // 代码逻辑 } }
静态方法加锁的锁和实例方法加锁的锁是不同的:
- 静态同步方法锁住的是类级别的
Class
对象。- 实例同步方法锁住的是具体对象级别的锁(即
this
)。因此,锁住静态方法并不会阻止其他线程访问实例方法,两个线程可以同时执行一个实例的
synchronized
实例方法和该类的synchronized
静态方法,因为它们锁住的是不同的对象。
# 4. synchronized 的底层实现
synchronized
的底层是通过进入和退出对象的 Monitor(监视器)来实现的,JVM 使用 monitorenter
和 monitorexit
指令来保证同步行为。
- Monitor:Monitor 是一个同步工具,可以理解为一个重量级锁。在进入
synchronized
方法或代码块时,线程会获取 Monitor,退出时会释放 Monitor。 - 偏向锁、轻量级锁和重量级锁:JVM 对
synchronized
的实现进行了优化,引入了偏向锁、轻量级锁和重量级锁来减少获取锁的开销,以提高性能。- 偏向锁:当一个线程多次获得同一个锁时,JVM 会将该锁偏向于这个线程,从而减少同步开销。
- 轻量级锁:当偏向锁被其他线程争夺时,会升级为轻量级锁。
- 重量级锁:当多个线程竞争激烈时,锁会升级为重量级锁,阻塞其他线程。
# 5. synchronized 与 ReentrantLock 的比较
- 可重入性:
synchronized
是可重入锁,意味着同一个线程可以多次获得同一把锁而不会发生死锁。例如,一个synchronized
方法调用另一个synchronized
方法时,线程不会被阻塞。 - 是否公平:
synchronized
是非公平锁,不能保证线程获取锁的顺序。而ReentrantLock
可以设置为公平锁,以保证等待时间最长的线程优先获得锁。 - 灵活性:
ReentrantLock
提供了更多灵活的功能,例如可以尝试获取锁、可中断锁等待、支持多个条件变量等。而synchronized
相对较为简单,不具备这些功能。 - 性能:在 Java 早期版本中,
synchronized
的性能相对较低,但在 JDK 1.6 之后,对synchronized
进行了大量优化,其性能与ReentrantLock
相差无几。
# 6. synchronized 的优缺点
- 优点:
- 语法简单,易于使用和理解。
- JVM 会自动管理锁的获取和释放,不容易出错。
- 缺点:
- 同步范围大时,可能会导致较高的性能开销。
synchronized
是非公平的,可能导致某些线程长期得不到锁,发生饥饿现象。- 没有提供显式的锁控制,功能相对较弱。
# 7. 适用场景
- 简单的同步场景:当需要对共享资源进行简单的线程同步操作时,使用
synchronized
是最为直接和便捷的选择。 - 多线程访问共享对象:适用于保护方法或代码块,保证同一时间只有一个线程可以执行,避免竞态条件的发生。
# 8. 总结
synchronized
关键字是 Java 并发编程中的重要工具之一,用于确保线程对共享资源的互斥访问。它使用 Java 的内置锁来管理线程的同步,通过保证方法或代码块在同一时间只能被一个线程执行,从而避免数据不一致的问题。尽管 synchronized
的功能相对简单,但在很多场景下,它是实现线程安全的首选方式。理解 synchronized
的底层机制和应用场景,有助于编写高效且健壮的多线程程序。
上次更新: 2024/11/01, 13:45:14