在Android开发(以及Java体系中),synchronized是用于保证线程安全的核心关键字,主要作用是实现同步,避免多个线程同时操作共享资源导致的数据不一致问题。以下从原理、用法、特性等方面详细解析:
一、synchronized的核心作用
synchronized的本质是通过加锁机制,确保同一时间只有一个线程能执行特定代码块(或方法),从而解决多线程环境下的竞态条件(多个线程同时读写共享资源导致的不可预期结果)。
示例:未同步的问题
public class Counter {
private int count = 0;
// 未加锁的自增方法
public void increment() {
count++; // 非原子操作(读取→加1→写入)
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 100个线程各执行1000次自增
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
}).start();
}
// 等待所有线程执行完毕
Thread.sleep(2000);
System.out.println("最终结果:" + counter.getCount()); // 预期100000,实际可能更小
}
}
问题:多个线程同时执行count++时,可能出现“读取旧值→同时加1→写入覆盖”的情况(如两个线程同时读取count=100,各自加1后都写入101,实际应变为102),导致结果小于预期。
二、synchronized的用法
synchronized可修饰方法或代码块,核心是通过“锁”控制线程访问权限。
1. 修饰实例方法(对象锁)
public synchronized void increment() { // 锁的是当前对象(this)
count++;
}
- 原理:锁的是调用该方法的对象实例。多个线程访问同一对象的
synchronized方法时,需竞争同一把锁;访问不同对象的方法时,互不干扰。
2. 修饰静态方法(类锁)
public static synchronized void staticMethod() { // 锁的是当前类的Class对象
// ...
}
- 原理:锁的是当前类的
Class对象(全局唯一)。无论创建多少实例,所有线程访问该静态方法时都竞争同一把锁。
3. 修饰代码块(显式指定锁对象)
public void increment() {
synchronized (this) { // 锁当前对象(与实例方法锁一致)
count++;
}
}
// 或指定其他对象作为锁
private final Object lock = new Object();
public void increment() {
synchronized (lock) { // 锁的是lock对象
count++;
}
}
- 原理:灵活指定锁对象,可缩小同步范围(只锁必要代码),减少锁竞争,提高性能。
三、synchronized的实现原理
synchronized的底层依赖JVM的监视器锁(Monitor) 实现,不同版本的JVM有不同优化(如偏向锁、轻量级锁、重量级锁),但核心逻辑一致:
-
锁的获取与释放:
线程进入synchronized代码块时,需获取锁(若锁被占用则阻塞等待);执行完毕后释放锁,让其他线程有机会获取。 -
可见性保证:
释放锁时,线程会将工作内存中的变量刷新到主内存;获取锁时,线程会清空工作内存,从主内存重新读取变量。因此,synchronized能保证共享变量的可见性(类似volatile的可见性效果,但更强)。 -
原子性保证:
同一时间只有一个线程执行同步代码块,确保代码块内的操作是原子性的(不可分割)。 -
禁止指令重排序:
同步块内的指令不会与块外的指令重排序,保证代码执行顺序符合预期。
四、synchronized的锁优化(JDK 6+)
为减少锁竞争的性能开销,JVM引入了锁升级机制,从低开销到高开销逐步升级:
| 锁类型 | 适用场景 | 原理 |
|---|---|---|
| 偏向锁 | 单线程重复获取同一把锁 | 只在第一次获取锁时设置偏向标记,后续无需竞争,直接进入(几乎无开销)。 |
| 轻量级锁 | 多线程交替获取锁,无激烈竞争 | 线程通过CAS(Compare And Swap)尝试获取锁,失败则自旋重试(不阻塞)。 |
| 重量级锁 | 多线程激烈竞争锁(自旋多次失败) | 依赖操作系统的互斥量(Mutex)实现,线程竞争失败会进入阻塞状态(开销大)。 |
- 优势:根据实际竞争情况动态调整锁类型,平衡安全性和性能。
五、synchronized与其他同步机制的对比
| 特性 | synchronized | volatile | Atomic类(如AtomicInteger) |
|---|---|---|---|
| 原子性 | ✅(代码块内操作) | ❌(仅单次读写) | ✅(基于CAS的原子操作) |
| 可见性 | ✅ | ✅ | ✅ |
| 有序性 | ✅(禁止重排序) | ✅(禁止部分重排序) | ❌(部分操作保证) |
| 适用场景 | 复杂逻辑(代码块同步) | 状态标志(简单读写) | 简单原子操作(自增、赋值等) |
| 性能 | 低竞争时接近volatile | 开销极小 | 低竞争时优于synchronized |
六、synchronized的使用注意事项
-
避免过度同步:
同步范围过大(如同步整个方法)会导致锁竞争激烈,降低性能。应只同步共享资源的操作部分(如count++而非整个方法)。 -
锁对象的选择:
- 避免使用
String、Integer等常量对象作为锁(可能导致不同代码块共用同一把锁,引发意外阻塞)。 - 推荐使用
private final Object lock = new Object()作为锁对象(唯一且不可变)。
- 避免使用
-
防止死锁:
多线程持有不同锁并相互等待时会导致死锁,例如:// 线程1:先锁a,再尝试锁b synchronized (a) { synchronized (b) { ... } } // 线程2:先锁b,再尝试锁a synchronized (b) { synchronized (a) { ... } }避免方式:按固定顺序获取锁(如线程1和线程2都先锁a再锁b)。
-
与
wait()/notify()配合使用:
在同步块中,可通过Object类的wait()(释放锁并等待)和notify()/notifyAll()(唤醒等待线程)实现线程间通信。例如:synchronized (lock) { while (conditionNotMet) { lock.wait(); // 释放锁,进入等待队列 } // 执行操作 lock.notify(); // 唤醒其他等待线程 }
七、synchronized在Android中的应用场景
- 共享数据修改:如计数器、列表操作等,需保证多线程修改的原子性(如
ListView的数据源更新)。 - 单例模式:双重检查锁定中,同步代码块保证实例创建的线程安全(配合
volatile)。 - 线程间通信:结合
wait()/notify()实现生产者-消费者模式(如Handler消息处理的底层同步)。 - 避免ANR:在UI线程中,若同步代码块执行时间过长,会导致UI卡顿甚至ANR,需注意同步代码的执行效率。
八、总结
synchronized是Java中最基础的同步机制,核心功能是保证原子性、可见性和有序性,通过锁机制控制线程对共享资源的访问。其优势是使用简单、安全性高(自动释放锁,避免死锁风险),但需注意合理使用以平衡性能。
在Android开发中,synchronized常用于处理多线程共享数据的场景,但需避免在UI线程中执行耗时同步操作,同时结合锁优化(如减小同步范围)提升性能。



459

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



