Java Thread线程2—线程锁synchronized,Lock,volatile

synchronized

在 Java 中,synchronized 关键字一共有 4 种写法(按“锁的对象”划分),所有写法在 字节码层面本质相同:通过 对象头里的 monitor 来实现互斥

写法 1:同步实例方法(锁的是当前实例 this

public class Counter {
    private int count = 0;

    public synchronized void increment() {   // 等价于 synchronized(this) { ... }
        count++;
    }
}
  • 锁对象:当前实例 this

  • 适用场景:多个线程操作同一个对象实例时保证线程安全。

同步静态方法(锁的是 Class 对象)

public class Counter {
    private static int count = 0;

    public static synchronized void increment() { // 等价于 synchronized(Counter.class) { ... }
        count++;
    }
}
  • 锁对象Counter.class(类的元数据对象)

  • 适用场景:保护静态变量,防止多个线程同时修改类级别的共享数据。

同步代码块(锁的是任意指定对象)

public class Counter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {   // 锁的是自定义的 lock 对象
            count++;
        }
    }
}
  • 锁对象:任意显式指定的对象(lockthisCounter.class 等)

  • 优点:粒度更细,减少锁竞争范围,提高并发度。

同步代码块(锁的是 this

public class Counter {
    private int count = 0;

    public void increment() {
        synchronized (this) {
            count++;
        }
    }
}

效果与“同步实例方法”完全相同,只是写法不同。

总结对照表(面试速记)

写法锁对象适用场景粒度
同步实例方法this保护实例变量
同步静态方法Class<?>保护静态变量
同步代码块(this)this与同步实例方法等价
同步代码块(任意对象)任意对象细粒度锁、减少竞争

锁与线程关系

只要多个方法共用同一把锁(比如默认的 this 或同一个 Class<?>),无论线程去访问哪一个带这把锁的方法,都必须串行地获取这把锁

举个例子

public class Demo {
    public synchronized void a() { /* ... */ }
    public synchronized void b() { /* ... */ }
    public synchronized void c() { /* ... */ }
}
  • 三个方法都隐式锁 this

  • 线程 T1 执行 a(),线程 T2 想执行 b(),线程 T3 想执行 c()

  • 结果:T2、T3 都会被挡在门外,直到 T1 释放锁

锁只认“对象”,不认“方法”;谁拿着同一把锁,谁就是临界区的唯一通行证。

Lock之ReentrantLock

基本范式(try-finally 必解锁)

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private final Lock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();          // 获取锁(阻塞直到成功)
        try {
            count++;
        } finally {
            lock.unlock();    // 必须放在 finally,防止异常死锁
        }
    }
}

最常用、最安全的写法;无论正常还是异常路径,都能保证解锁。

可中断锁(lockInterruptibly()

public void incrementWithInterrupt() throws InterruptedException {
    lock.lockInterruptibly();  // 可被 Thread.interrupt() 打断
    try {
        count++;
    } finally {
        lock.unlock();
    }
}

尝试非阻塞锁(tryLock()

public boolean tryIncrement() {
    if (lock.tryLock()) {      // 立即返回,不阻塞
        try {
            count++;
            return true;
        } finally {
            lock.unlock();
        }
    } else {
        System.out.println("没抢到锁,直接干别的");
        return false;
    }
}

常用于 避免死锁带超时的快速失败 逻辑。

超时尝试锁(tryLock(long, TimeUnit)

public boolean incrementWithTimeout(long time, TimeUnit unit) throws InterruptedException {
    if (lock.tryLock(time, unit)) {   // 最多等待指定时间
        try {
            count++;
            return true;
        } finally {
            lock.unlock();
        }
    } else {
        System.out.println("等待超时,没抢到锁");
        return false;
    }
}

对比 synchronized 与 Lock

特性synchronizedLock (ReentrantLock)
语法关键字API 调用
可中断✅ (lockInterruptibly)
非阻塞尝试✅ (tryLock)
超时等待✅ (tryLock(timeout))
公平策略✅ (new ReentrantLock(true))
可绑定多个条件队列✅ (newCondition)

volatile

volatile 是 Java 提供的最轻量级同步机制,只保证“可见性”和“有序性”不保证“原子性”。在 Android 开发里,正确使用可以省去不少 synchronizedLock 的开销;用错则会埋坑

volatile 解决的是“一个线程改了,另一个线程立刻能看到”的问题,不能解决“多线程同时改”的问题。

作用点是否保证说明
可见性写后立即刷新到主存,读时从主存取
有序性(禁止重排)插入内存屏障,防止指令重排
原子性复合操作(i++、check-then-act)仍不安全

Android 典型使用场景

状态标志位

线程 A 改标志,线程 B 立即读,只有一个线程写

private volatile boolean isRunning = true;

public void stop() {
    isRunning = false;   // 线程 A
}

public void loop() {
    while (isRunning) {  // 线程 B
        ...
    }
}

双重检查单例

class Singleton {
    private static volatile Singleton INSTANCE;

    public static Singleton get() {
        if (INSTANCE == null) {           // 1st check
            synchronized (Singleton.class) {
                if (INSTANCE == null) {   // 2nd check
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

UI 可见性通信

Activity / Fragment 中后台线程更新单个变量状态:

private volatile int mProgress;  // 下载进度

synchronized 的对比

维度volatilesynchronized
作用范围单个变量代码块/方法
原子性
阻塞不会
性能极高较低(有优化

volatile 能保证线程之间立即可见禁止指令重排,适合一写多读纯标志位或双重检查单例;一旦涉及复合操作,仍需 synchronizedLockAtomic* 来保证原子性。”

禁止重排

没有 volatile,DCL 可能因为“先赋值、后初始化”的重排而暴露半初始化对象;volatile 的内存屏障把这两步顺序钉死,彻底杜绝该隐患。

重排前后对比

正常顺序(程序员视角)重排后(编译器/CPU 视角)
1. memory = allocate()1. memory = allocate()
2. ctor(memory)3. instance = memory ← 引用先发布
3. instance = memory2. ctor(memory) ← 初始化延后

带来的并发 bug

// 线程 A 执行
instance = new Singleton();   // 内部被重排成 1→3→2

// 线程 B 几乎同时
if (instance != null) {       // 看到非 null
    instance.method();        // 实际对象还没初始化 → 崩溃或数据错误
}

加上 volatile

  • 读写都插入 内存屏障,禁止上述重排。

  • 线程 B 一定 看到“构造完成”的对象。

android中锁的使用

android 中使用lock锁多还是synchronized?

Android 日常业务代码 中,synchronized 远比 Lock 用得多;只有在需要 可中断、超时、公平锁、读写分离 等高级特性时,才会显式使用 ReentrantLock / ReentrantReadWriteLock。

统计数据与社区习惯

  • Google 官方示例、AOSP、Jetpack 源码里,95% 以上的同步场景都是 synchronizedvolatile

  • Kotlin 标准库提供的 @Synchronizedsynchronized(lock){} 进一步降低了使用门槛

  • 高并发框架(如 OkHttp、Glide)内部可能用到 ReentrantLock,但普通业务层很少需要。

特点对锁选择的影响
UI 主线程不容阻塞一般把耗时任务丢到线程池/HandlerThread,锁竞争通常较轻;synchronized 足够。
电量 & 内存敏感synchronized 在 ART 下经过 偏向锁、轻量级锁 优化,低开销;ReentrantLock 需要额外对象和队列,内存占用略高。
代码简洁性Android 开发节奏快,“写起来简单、维护成本低” 比极致性能更重要。

“在 Android 项目中,synchronized 是首选,简单可靠且 ART 已做大量优化;只有遇到 高并发读写、可中断、公平锁 等需求,才考虑 ReentrantLock 或 ReentrantReadWriteLock。”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值