☰
多线程:synchronized(锁)
2026/10/22 6:57:58 网站建设 项目流程

在多线程编程中,当我们有多个线程同时访问共享资源时,就会出现数据竞争的问题。想象一下,你和家人共享一个银行账户,如果你们同时在不同ATM机上取钱,系统该如何保证余额的正确性?

为了解决上述问题,synchronized出现了,它是Java中解决多线程并发问题最常用、最基础的同步机制,你也可以称呼它为锁机制。

一、synchronized的核心特性

1.1 原子性(Atomicity)
`synchronized`保证了被它修饰的代码块或方法在同一时刻只能被一个线程执行,从而确保了操作的原子性。

public class Counter {
private int count = 0;

// 没有同步 - 线程不安全
public void increment() {
count++; // 这实际上包含三个操作:读取、增加、写入
}

// 使用synchronized - 线程安全
public synchronized void safeIncrement() {
count++;
}
}

1.2 可见性(Visibility)
当一个线程释放锁时,JVM会强制将工作内存中的修改刷新到主内存中。当另一个线程获取锁时,JVM会强制它清空工作内存,从主内存重新加载变量。

1.3 有序性(Ordering)
`synchronized`限制了指令重排序,保证了在同步代码块内的指令不会与同步代码块外的指令重排序。

以上三种便是锁的三种主要机制。

二、synchronized的三种使用方式

2.1 同步实例方法

public class BankAccount {
private double balance;

// 同步实例方法 - 锁是当前实例对象(this)
public synchronized void deposit(double amount) {
balance += amount;
}

public synchronized void withdraw(double amount) {
if (balance >= amount) {
balance -= amount;
}
}

public synchronized double getBalance() {
return balance;
}
}

使用场景:当多个线程操作同一个对象实例时。


2.2 同步静态方法

public class IdGenerator {
private static long id = 0;

// 同步静态方法 - 锁是当前类的Class对象
public static synchronized long generateId() {
return id++;
}
}

使用场景:当多个线程需要访问类的静态成员时。

2.3 同步代码块

public class FineGrainedSync {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
private int counter1 = 0;
private int counter2 = 0;

public void increment1() {
synchronized (lock1) {
counter1++;
}
}

public void increment2() {
synchronized (lock2) {
counter2++;
}
}
}

优势:可以实现更细粒度的锁控制,减少锁竞争,提高并发性能。

三、synchronized的锁机制演进

3.1 JDK 1.6之前的重量级锁

早期版本中,synchronized直接对应操作系统的互斥锁(Mutex Lock),每次加锁和解锁都需要从用户态切换到内核态,性能开销很大。

3.2 锁升级过程(JDK 1.6+)

现代JVM中,synchronized锁会经历一个逐步升级的过程:

无锁状态
对象刚创建时的初始状态。

偏向锁(Biased Locking)
- 场景:只有一个线程访问同步块
- 原理:在对象头中记录偏向的线程ID,该线程再次访问时无需CAS操作
- 优点:几乎没有同步开销

// 大多数情况下,我们的同步方法其实只有一个线程访问
public class Logger {
private static final Logger instance = new Logger();

public static Logger getInstance() {
return instance;
}

// 对于单例模式,偏向锁非常高效
public synchronized void log(String message) {
System.out.println(message);
}
}

轻量级锁(Lightweight Locking)
- 场景:多个线程交替访问同步块,但没有真正同时竞争
- 原理:通过CAS操作在栈帧中创建锁记录(Lock Record)
- 优点:避免操作系统层面的线程阻塞

重量级锁(Heavyweight Locking)
- 场景:多个线程真正同时竞争锁
- 原理:使用操作系统的互斥量,竞争失败的线程进入阻塞状态
- 缺点:性能开销大,涉及用户态到内核态的切换

四、synchronized的注意事项

4.1 避免死锁
public class DeadlockDemo {
private final Object lockA = new Object();
private final Object lockB = new Object();

public void method1() {
synchronized (lockA) {
synchronized (lockB) {
// 业务逻辑
}
}
}

public void method2() {
synchronized (lockB) { // 与method1的加锁顺序相反,可能导致死锁
synchronized (lockA) {
// 业务逻辑
}
}
}
}

解决方案:统一加锁顺序,或使用`tryLock`等机制。

4.2 性能考虑

// 不推荐的写法 - 锁粒度太粗
public synchronized void processData(List<String> data) {
for (String item : data) {
// 处理每个数据项,耗时操作
expensiveOperation(item);
}
}

// 推荐的写法 - 减小锁粒度
public void processDataBetter(List<String> data) {
List<String> results = new ArrayList<>();
for (String item : data) {
String result = expensiveOperation(item); // 无锁操作
synchronized (this) {
results.add(result); // 只在必要时加锁
}
}
return results;
}

关键要点:
1. 根据场景选择合适的同步方式:实例方法、静态方法或同步块
2. 尽量减小锁的粒度,提高并发性能
3. 了解锁升级过程,避免不必要的锁竞争
4. 注意死锁问题,保持一致的加锁顺序

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询