synchronized
关键字是 Java 提供的用于解决并发编程中数据一致性问题的重要工具。它通过锁机制确保在同一时刻只有一个线程能够执行被同步的方法或代码块,从而实现互斥访问。尽管 synchronized 使用简单且可靠,但在高并发场景下可能会带来性能开销,需要谨慎使用。
特性
- 原子性:synchronized 确保在同一时刻只有一个线程能够执行被同步的方法或代码块,从而实现原子性操作。
- 可见性:synchronized 确保在一个线程释放锁之前,对共享变量所做的修改对其他线程是可见的。
- 互斥锁:synchronized 通过锁机制实现线程间的互斥访问,防止多个线程同时执行被同步的代码。
- 锁粒度:synchronized 可以作用于方法或代码块,锁粒度灵活。
用法
- 同步方法:synchronized 修饰整个方法。
- 同步代码块:synchronized 修饰特定的代码块,通常使用一个锁对象。
public class Counter {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
// 同步代码块
public void decrement() {
synchronized (this) {
count--;
}
}
public synchronized int getCount() {
return count;
}
}
锁对象:对于同步实例方法和同步代码块,锁对象是当前实例 (this) 或显式指定的对象。
类锁:对于同步静态方法,锁对象是当前类的 Class 对象。
同步静态方法:使用类锁进行同步,确保在同一时刻只有一个线程能够执行该静态方法。
public class Counter {
private static int count = 0;
// 同步静态方法
public static synchronized void increment() {
count++;
}
// 同步静态方法
public static synchronized int getCount() {
return count;
}
}
使用场景
- 需要保证方法或代码块在同一时刻只能被一个线程执行。
- 需要确保多个线程对共享资源的操作是原子性的。
- 需要确保一个线程对共享变量的修改对其他线程是可见的。
synchronized 的优缺点
优点
简单易用:synchronized 是 Java 的内置关键字,使用简单,语法清晰。
可靠性高:synchronized 由 JVM 实现,能够确保线程安全性和数据一致性。
缺点
性能开销:由于 synchronized 需要获取和释放锁,可能会导致性能开销,尤其是在高并发场景下。
可能导致死锁:如果多个线程在不同顺序上获取多个锁,可能会导致死锁。
volatile
特性
- 可见性:volatile 确保对变量的修改对所有线程立即可见。
- 非原子性:volatile 只能保证变量的可见性,不能保证原子性操作。
- 轻量级:volatile 是一种轻量级的同步机制,不会引入锁机制,因此不会导致线程阻塞。
性能开销相对较小,因为它不涉及锁机制,只是确保了内存可见性。
用法
修饰变量:volatile 只能用于修饰变量,确保对该变量的读写操作对所有线程是可见的
public class Flag {
private volatile boolean flag = false;
public void setFlag(boolean value) {
flag = value;
}
public boolean getFlag() {
return flag;
}
}
使用场景
- 需要确保变量的可见性,但不需要保证操作的原子性。
- 适用于状态标志、配置参数等简单变量的读写操作。
- 适用于单次读 / 写操作,而不是复合操作(如递增、自增等)。
Lock
特性
原子性:Lock 提供了显式的锁机制,确保对共享资源的原子性操作。
可见性:Lock 确保在一个线程释放锁之前,对共享变量所做的修改对其他线程是可见的。
灵活性:Lock 提供了比 synchronized 更灵活的锁机制,可以显式地获取和释放锁,还提供了可中断锁获取、超时锁获取等功能。
用法
显式锁:Lock 接口及其实现类(如 ReentrantLock)提供了显式的锁机
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
使用场景
- 需要显式地控制锁的获取和释放。
- 需要支持可中断的锁获取、超时锁获取等高级功能。
- 需要在复杂的并发场景中提供更细粒度的锁控制。
总结
相同点
- synchronized 和 Lock 都能确保线程对共享资源的原子性操作和可见性。
- synchronized 和 volatile 都能确保共享变量的可见性。
不同点
- synchronized 和 Lock 提供了原子性和可见性,而 volatile 只能提供可见性。
- synchronized 是隐式锁机制,使用简单,但可能导致性能开销较大;Lock 是显式锁机制,使用灵活,但需要手动获取和释放锁。
- volatile 是一种轻量级的同步机制,不会引入锁机制,因此不会导致线程阻塞,但不能保证复合操作的原子性。
在内存开销方面 synchronized >lock>volatile