在Java中,synchronized 关键字是实现线程同步的基础工具,但其使用需要结合场景和性能考量。
一、选择合适的锁粒度
原则:在保证线程安全的前提下,尽量缩小同步范围,避免锁持有时间过长。
1. 方法级同步(粗粒度)
- 适用场景:整个方法的逻辑都需要同步。
- 示例:
public class Counter { private int count = 0; // 同步整个方法(锁对象为this) public synchronized void increment() { count++; } }
2. 代码块级同步(细粒度)
- 适用场景:仅部分代码需要同步。
- 示例:
public class Calculator { private final Object lock = new Object(); // 专用锁对象 private int result = 0; public void compute() { // 非关键代码(无需同步) preProcess(); // 仅关键部分加锁 synchronized (lock) { result = performCriticalCalculation(); } // 非关键代码(无需同步) postProcess(); } }
二、优先使用专用锁对象
原则:避免使用 this 或类对象(Class)作为锁,减少锁竞争范围。
1. 错误示例(使用this)
public class BadLockExample {
private final List<String> list = new ArrayList<>();
public void add(String item) {
synchronized (this) { // 锁范围过大,可能与其他方法冲突
list.add(item);
}
}
public void otherOperation() {
synchronized (this) { // 与add()共享锁,可能导致不必要的阻塞
// 其他操作
}
}
}
2. 正确示例(专用锁对象)
public class GoodLockExample {
private final List<String> list = new ArrayList<>();
private final Object listLock = new Object(); // 专门用于保护list的锁
public void add(String item) {
synchronized (listLock) { // 仅保护list相关操作
list.add(item);
}
}
public void otherOperation() {
// 无需同步或使用其他锁
}
}
三、静态同步方法与类锁
原则:静态同步方法使用类锁(Class 对象),与实例锁互不干扰。
1. 静态同步方法
public class StaticCounter {
private static int count = 0;
// 静态同步方法(锁对象为StaticCounter.class)
public static synchronized void increment() {
count++;
}
}
2. 实例方法与静态方法的锁隔离
public class MixedLockExample {
private int instanceCount = 0;
private static int staticCount = 0;
// 实例方法锁(this)
public synchronized void instanceMethod() {
instanceCount++;
}
// 静态方法锁(MixedLockExample.class)
public static synchronized void staticMethod() {
staticCount++;
}
}
四、双重检查锁定(DCL)模式
场景:实现线程安全的懒汉式单例,需结合 volatile 关键字。
1. 错误示例(未使用volatile)
public class UnsafeSingleton {
private static UnsafeSingleton instance;
private UnsafeSingleton() {}
public static UnsafeSingleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (UnsafeSingleton.class) {
if (instance == null) { // 第二次检查
instance = new UnsafeSingleton(); // 可能因重排序导致其他线程看到未初始化的对象
}
}
}
return instance;
}
}
2. 正确示例(使用volatile)
public class SafeSingleton {
private static volatile SafeSingleton instance; // 禁止指令重排序
private SafeSingleton() {}
public static SafeSingleton getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (SafeSingleton.class) {
if (instance == null) { // 第二次检查
instance = new SafeSingleton(); // 确保初始化完成后才对其他线程可见
}
}
}
return instance;
}
}
五、生产者 - 消费者模型
场景:使用 wait()/notify() 实现线程间协作,需在同步块内调用。
1. 基于 synchronized 的实现
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumer {
private final Queue<Integer> queue = new LinkedList<>();
private final int MAX_SIZE = 10;
// 生产者方法
public void produce(int item) throws InterruptedException {
synchronized (queue) { // 锁对象为共享队列
while (queue.size() == MAX_SIZE) {
queue.wait(); // 队列满时等待
}
queue.add(item);
queue.notifyAll(); // 通知可能等待的消费者
}
}
// 消费者方法
public int consume() throws InterruptedException {
synchronized (queue) {
while (queue.isEmpty()) {
queue.wait(); // 队列空时等待
}
int item = queue.poll();
queue.notifyAll(); // 通知可能等待的生产者
return item;
}
}
}
2. 优化:使用 notify() 替代 notifyAll()
public void produce(int item) throws InterruptedException {
synchronized (queue) {
while (queue.size() == MAX_SIZE) {
queue.wait();
}
boolean wasEmpty = queue.isEmpty();
queue.add(item);
if (wasEmpty) { // 仅在队列从空变为非空时通知消费者
queue.notify();
}
}
}
六、集合类的线程安全封装
场景:将非线程安全的集合转换为线程安全的实现。
1. 手动封装
import java.util.ArrayList;
import java.util.List;
public class SynchronizedList<E> {
private final List<E> list = new ArrayList<>();
private final Object lock = new Object();
public void add(E element) {
synchronized (lock) {
list.add(element);
}
}
public E get(int index) {
synchronized (lock) {
return list.get(index);
}
}
public int size() {
synchronized (lock) {
return list.size();
}
}
}
2. 使用 Collections.synchronizedXXX
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class WrapperExample {
private final List<String> list = Collections.synchronizedList(new ArrayList<>());
public void add(String item) {
list.add(item); // 内部已同步
}
public void iterate() {
synchronized (list) { // 迭代时需手动同步
for (String item : list) {
// 处理元素
}
}
}
}
七、性能优化技巧
-
减少锁持有时间:
public void optimizedMethod() { // 非关键代码(无需同步) Object result; synchronized (this) { result = fetchData(); // 仅同步关键操作 } process(result); // 非关键代码 } -
锁分段技术(适用于多资源独立操作):
public class StripedLockExample { private final Object[] locks = new Object[16]; private final Data[] data = new Data[16]; public StripedLockExample() { for (int i = 0; i < 16; i++) { locks[i] = new Object(); data[i] = new Data(); } } public void update(int index) { synchronized (locks[index % 16]) { // 不同索引使用不同锁 data[index % 16].update(); } } }
八、常见误区与注意事项
-
避免在循环中同步:
// 错误:每次循环都获取锁 for (int i = 0; i < 1000; i++) { synchronized (lock) { count++; } } // 正确:仅在必要时同步 synchronized (lock) { for (int i = 0; i < 1000; i++) { count++; } } -
不要使用字符串常量或包装类作为锁:
public void badLock() { synchronized ("LOCK") { // 字符串常量池可能导致意外的锁竞争 // ... } }
九、总结
- 锁粒度:优先使用代码块同步,避免方法级同步。
- 锁对象:使用专用锁对象(
private final Object),避免this或类对象。 - 原子性操作:对于简单原子操作,考虑使用
Atomic类替代synchronized。 - 线程协作:在同步块内使用
wait()/notify(),优先使用notify()减少唤醒线程数量。 - 性能考量:高并发场景下可考虑使用
ReentrantLock或并发容器。
通过合理设计锁的范围和对象,结合线程协作机制,synchronized 关键字可以优雅且高效地解决多线程同步问题。
350



被折叠的 条评论
为什么被折叠?



