Android中synchronized关键字怎么理解?

在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有不同优化(如偏向锁、轻量级锁、重量级锁),但核心逻辑一致:

  1. 锁的获取与释放
    线程进入synchronized代码块时,需获取锁(若锁被占用则阻塞等待);执行完毕后释放锁,让其他线程有机会获取。

  2. 可见性保证
    释放锁时,线程会将工作内存中的变量刷新到主内存;获取锁时,线程会清空工作内存,从主内存重新读取变量。因此,synchronized能保证共享变量的可见性(类似volatile的可见性效果,但更强)。

  3. 原子性保证
    同一时间只有一个线程执行同步代码块,确保代码块内的操作是原子性的(不可分割)。

  4. 禁止指令重排序
    同步块内的指令不会与块外的指令重排序,保证代码执行顺序符合预期。

四、synchronized的锁优化(JDK 6+)

为减少锁竞争的性能开销,JVM引入了锁升级机制,从低开销到高开销逐步升级:

锁类型适用场景原理
偏向锁单线程重复获取同一把锁只在第一次获取锁时设置偏向标记,后续无需竞争,直接进入(几乎无开销)。
轻量级锁多线程交替获取锁,无激烈竞争线程通过CAS(Compare And Swap)尝试获取锁,失败则自旋重试(不阻塞)。
重量级锁多线程激烈竞争锁(自旋多次失败)依赖操作系统的互斥量(Mutex)实现,线程竞争失败会进入阻塞状态(开销大)。
  • 优势:根据实际竞争情况动态调整锁类型,平衡安全性和性能。

五、synchronized与其他同步机制的对比

特性synchronizedvolatileAtomic类(如AtomicInteger
原子性✅(代码块内操作)❌(仅单次读写)✅(基于CAS的原子操作)
可见性
有序性✅(禁止重排序)✅(禁止部分重排序)❌(部分操作保证)
适用场景复杂逻辑(代码块同步)状态标志(简单读写)简单原子操作(自增、赋值等)
性能低竞争时接近volatile开销极小低竞争时优于synchronized

六、synchronized的使用注意事项

  1. 避免过度同步
    同步范围过大(如同步整个方法)会导致锁竞争激烈,降低性能。应只同步共享资源的操作部分(如count++而非整个方法)。

  2. 锁对象的选择

    • 避免使用StringInteger常量对象作为锁(可能导致不同代码块共用同一把锁,引发意外阻塞)。
    • 推荐使用private final Object lock = new Object()作为锁对象(唯一且不可变)。
  3. 防止死锁
    多线程持有不同锁并相互等待时会导致死锁,例如:

    // 线程1:先锁a,再尝试锁b
    synchronized (a) {
        synchronized (b) { ... }
    }
    
    // 线程2:先锁b,再尝试锁a
    synchronized (b) {
        synchronized (a) { ... }
    }
    

    避免方式:按固定顺序获取锁(如线程1和线程2都先锁a再锁b)。

  4. wait()/notify()配合使用
    在同步块中,可通过Object类的wait()(释放锁并等待)和notify()/notifyAll()(唤醒等待线程)实现线程间通信。例如:

    synchronized (lock) {
        while (conditionNotMet) {
            lock.wait(); // 释放锁,进入等待队列
        }
        // 执行操作
        lock.notify(); // 唤醒其他等待线程
    }
    

七、synchronized在Android中的应用场景

  1. 共享数据修改:如计数器、列表操作等,需保证多线程修改的原子性(如ListView的数据源更新)。
  2. 单例模式:双重检查锁定中,同步代码块保证实例创建的线程安全(配合volatile)。
  3. 线程间通信:结合wait()/notify()实现生产者-消费者模式(如Handler消息处理的底层同步)。
  4. 避免ANR:在UI线程中,若同步代码块执行时间过长,会导致UI卡顿甚至ANR,需注意同步代码的执行效率。

八、总结

synchronized是Java中最基础的同步机制,核心功能是保证原子性、可见性和有序性,通过锁机制控制线程对共享资源的访问。其优势是使用简单、安全性高(自动释放锁,避免死锁风险),但需注意合理使用以平衡性能。

在Android开发中,synchronized常用于处理多线程共享数据的场景,但需避免在UI线程中执行耗时同步操作,同时结合锁优化(如减小同步范围)提升性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值