当前位置:首页 > 后端开发 > 正文

如何在Java中使用锁?

在Java中通过synchronized关键字或Lock接口实现线程同步,synchronized可修饰方法或代码块,自动管理锁;Lock需显式调用lock()/unlock(),提供更灵活的加锁机制,如尝试获取锁、超时控制等,确保多线程安全访问共享资源。

为什么需要锁?

当多个线程同时读写共享数据(如静态变量、对象属性)时,可能引发:

  • 数据竞争:线程A修改数据时,线程B同时读取导致脏数据。
  • 状态不一致:非原子操作(如i++包含读-改-写三步)被线程交替执行。
    锁通过互斥性确保同一时刻仅一个线程访问资源,并保证修改后的值对其他线程可见

内置锁:synchronized

synchronized是Java最基础的锁机制,JVM自动管理锁的获取与释放。

同步代码块

public class Counter {
    private int count = 0;
    private final Object lock = new Object(); // 锁对象
    public void increment() {
        synchronized (lock) { // 对lock对象加锁
            count++; // 临界区代码
        }
    }
}
  • 锁对象:可以是任意对象(如lock),推荐使用私有final对象避免外部干扰。
  • 作用范围:仅包裹需要互斥的代码(临界区),减少锁竞争。

同步方法

public synchronized void increment() { 
    count++; // 等同于锁住当前对象实例(this)
}
public static synchronized void staticMethod() { 
    // 锁住当前类的Class对象(Counter.class)
}
  • 实例方法:锁对象是当前实例(this)。
  • 静态方法:锁对象是类的Class对象(如Counter.class)。

显式锁:Lock接口

java.util.concurrent.locks.Lock提供更灵活的锁控制,需手动释放锁,常用实现类为ReentrantLock

基础用法

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SafeCounter {
    private final Lock lock = new ReentrantLock(); // 创建显式锁
    private int value = 0;
    public void add(int num) {
        lock.lock(); // 获取锁
        try {
            value += num; // 临界区
        } finally {
            lock.unlock(); // 必须在finally中释放锁
        }
    }
}
  • 优势:支持尝试获取锁、超时锁、可中断锁等高级功能。
  • 注意unlock()必须放在finally块中,防止异常导致锁未释放。

高级功能

  • 尝试锁(tryLock):避免线程阻塞

    如何在Java中使用锁?  第1张

    if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试等待1秒
        try { /* 操作资源 */ } 
        finally { lock.unlock(); }
    } else {
        System.out.println("获取锁失败,执行备选逻辑");
    }
  • 可中断锁:响应线程中断

    public void interruptibleLock() throws InterruptedException {
        lock.lockInterruptibly(); // 若线程被中断,抛出InterruptedException
        try { /* ... */ } 
        finally { lock.unlock(); }
    }
  • 公平锁:减少线程饥饿

    Lock fairLock = new ReentrantLock(true); // true表示公平锁

    公平锁按请求顺序分配锁,但可能降低吞吐量(默认非公平锁)。


锁的选择策略

场景 推荐锁类型 理由
简单同步需求(如单方法互斥) synchronized 代码简洁,JVM自动优化
需要超时/尝试锁、可中断锁 ReentrantLock 灵活控制锁行为
高并发且锁竞争激烈 ReentrantLock 结合tryLock可避免线程阻塞
读多写少场景 ReentrantReadWriteLock 读写分离提升并发性(非本文重点)

避坑指南

  1. 死锁预防

    • 避免嵌套锁:线程A持有锁1请求锁2,线程B持有锁2请求锁1。
    • 统一加锁顺序:所有线程按固定顺序获取锁(如先锁A再锁B)。
    • 使用tryLock设置超时。
  2. 锁粒度优化

    • 细化锁:锁住最小必要代码块(如synchronized块优于同步方法)。
    • 分离锁:对无关联资源使用不同锁对象(如lockForDatalockForLog)。
  3. 性能考量

    • 避免在锁中执行耗时操作(如IO操作)。
    • 优先使用线程安全类(如ConcurrentHashMap代替synchronized+HashMap)。

  • 基础场景:优先用synchronized,简洁安全。
  • 复杂需求:选择ReentrantLock,利用其超时、中断等特性增强健壮性。
  • 核心原则:锁范围最小化、避免嵌套、及时释放。

引用说明: 参考Oracle官方文档Java Concurrency、Brian Goetz所著《Java并发编程实战》(Java Concurrency in Practice),以及Java API文档Lock接口,实践代码基于JDK 8+验证。

0