Java 锁机制

一、Java 锁机制的核心分类

Java 中的锁主要分为内置锁(synchronized)显式锁(Lock 接口) 两大类,衍生出多种适配不同场景的锁类型,先看核心分类框架:

Java锁机制

内置锁 synchronized

显式锁 Lock接口

偏向锁

轻量级锁

重量级锁

ReentrantLock 可重入锁

ReentrantReadWriteLock 读写锁

StampedLock 戳记锁

其他特殊锁

公平锁/非公平锁

可重入锁/不可重入锁

乐观锁/悲观锁

二、核心锁类型详解(附代码示例)

1. 内置锁:synchronized(最基础、易用)

核心特点 :JVM 层面实现的隐式锁,自动加锁 / 释放锁,可重入,从 JDK 1.6 开始引入偏向锁、轻量级锁优化,避免无脑升级为重量级锁。适用场景:简单并发场景,代码简洁,无需手动释放锁。

代码示例

java

运行

复制代码
public class SynchronizedDemo {
    // 1. 修饰方法(锁对象是当前实例)
    public synchronized void methodLock() {
        System.out.println("方法级锁:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 2. 修饰静态方法(锁对象是类的Class对象)
    public static synchronized void staticMethodLock() {
        System.out.println("静态方法级锁:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 3. 修饰代码块(自定义锁对象)
    public void blockLock() {
        Object lock = new Object();
        synchronized (lock) {
            System.out.println("代码块锁:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        // 测试方法锁(多线程竞争同一实例锁)
        new Thread(demo::methodLock, "线程1").start();
        new Thread(demo::methodLock, "线程2").start();
        // 测试静态方法锁
        new Thread(SynchronizedDemo::staticMethodLock, "线程3").start();
        new Thread(SynchronizedDemo::staticMethodLock, "线程4").start();
    }
}

执行结果 :同一类型的锁会串行执行,比如线程 1 执行完 methodLock 后,线程 2 才会执行。

2. 显式锁:ReentrantLock(灵活可控)

核心特点 :JUC 包下的显式锁,需手动 lock() 加锁、unlock() 释放锁(建议放 finally 块),支持公平锁 / 非公平锁、可中断、超时获取锁。适用场景:复杂并发场景,需要灵活控制锁的获取 / 释放。

代码示例

java

运行

复制代码
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
    // 创建非公平锁(默认),公平锁传 true:new ReentrantLock(true)
    private final ReentrantLock lock = new ReentrantLock();

    public void doTask() {
        // 加锁
        lock.lock();
        try {
            System.out.println("ReentrantLock加锁:" + Thread.currentThread().getName());
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁(必须放finally,避免死锁)
            lock.unlock();
            System.out.println("ReentrantLock释放锁:" + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        ReentrantLockDemo demo = new ReentrantLockDemo();
        new Thread(demo::doTask, "线程A").start();
        new Thread(demo::doTask, "线程B").start();
    }
}
3. 读写锁:ReentrantReadWriteLock(读写分离,提升并发)

核心特点 :分为读锁(共享锁)和写锁(排他锁),多个读线程可同时获取读锁,写线程独占锁,解决 "多读少写" 场景的性能问题。适用场景:缓存、配置读取等多读少写的场景。

代码示例

java

运行

复制代码
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
    private final Map<String, String> cache = new HashMap<>();
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    // 读锁(共享)
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    // 写锁(排他)
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

    // 读操作:加读锁
    public String get(String key) {
        readLock.lock();
        try {
            System.out.println("读锁:" + Thread.currentThread().getName() + " 读取key=" + key);
            return cache.get(key);
        } finally {
            readLock.unlock();
        }
    }

    // 写操作:加写锁
    public void put(String key, String value) {
        writeLock.lock();
        try {
            System.out.println("写锁:" + Thread.currentThread().getName() + " 写入key=" + key);
            cache.put(key, value);
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        ReadWriteLockDemo demo = new ReadWriteLockDemo();
        // 多个读线程可同时执行
        new Thread(() -> demo.get("name"), "读线程1").start();
        new Thread(() -> demo.get("name"), "读线程2").start();
        // 写线程独占,读线程需等待写锁释放
        new Thread(() -> demo.put("name", "Java锁机制"), "写线程1").start();
        new Thread(() -> demo.get("name"), "读线程3").start();
    }
}

执行结果:读线程 1、2 同时执行,写线程 1 执行时,所有读 / 写线程等待,写锁释放后读线程 3 执行。

4. 乐观锁 vs 悲观锁(思想层面)
  • 悲观锁:认为每次操作都会竞争,先加锁再执行(synchronized、ReentrantLock 都是),适合写多读少场景。
  • 乐观锁:认为不会有竞争,不加锁,通过 CAS(Compare And Swap)机制保证原子性,适合读多写少场景(如 AtomicInteger、数据库版本号)。

乐观锁代码示例(AtomicInteger)

java

运行

复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class OptimisticLockDemo {
    private final AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        // CAS 原子操作:比较并替换
        count.incrementAndGet();
        System.out.println(Thread.currentThread().getName() + " count=" + count.get());
    }

    public static void main(String[] args) {
        OptimisticLockDemo demo = new OptimisticLockDemo();
        for (int i = 0; i < 5; i++) {
            new Thread(demo::increment, "线程" + i).start();
        }
    }
}

三、关键锁特性对比

锁类型 核心优势 缺点 适用场景
synchronized 简洁、自动释放、JVM 优化 灵活性低、无法中断 简单并发、低复杂度场景
ReentrantLock 灵活、可中断、公平 / 非公平 需手动释放、代码稍复杂 复杂并发、需精细控制锁
ReentrantReadWriteLock 读写分离、高并发读 写锁独占、易产生写饥饿 多读少写(缓存、配置)
乐观锁(CAS) 无锁竞争、性能高 高并发下 CAS 失败重试耗 CPU 读多写少、低冲突场景

四、锁机制避坑要点

  1. 死锁:多个线程互相持有对方需要的锁,避免方式:按固定顺序加锁、设置锁超时、使用 tryLock ()。
  2. 锁升级:synchronized 会从偏向锁→轻量级锁→重量级锁升级,不可逆,高并发下尽量减少锁竞争。
  3. 锁粒度:避免锁范围过大(如整个方法加锁),尽量缩小锁代码块,只锁关键逻辑。

总结

  1. 基础场景用 synchronized:代码简洁,JVM 优化足够应对大部分简单并发,无需手动管理锁。
  2. 复杂场景用 ReentrantLock / 读写锁:需要中断、超时、公平锁或读写分离时,选择显式锁更灵活
相关推荐
czlczl200209252 小时前
Spring Security 6 :配置生产级 SecurityFilterChain
java·spring
龘龍龙2 小时前
Python基础学习(六)
开发语言·python·学习
Java小白,一起学习2 小时前
AndroidStudio安装教程
java·android-studio
学编程就要猛2 小时前
算法:3.快乐数
java·算法
高山上有一只小老虎2 小时前
如何下载并使用Memory Analyzer (MAT)
java·jvm
华仔啊2 小时前
Java 开发必看:什么时候用 for,什么时候用 Stream?
java·后端
未来之窗软件服务2 小时前
幽冥大陆(五十八)php1024位密码生成—东方仙盟筑基期
开发语言·算法·仙盟创梦ide·东方仙盟
刺客xs2 小时前
Qt ----- QT线程
开发语言·qt
夏幻灵2 小时前
C++ 里 什么时候不用指针,而选择值拷贝/深拷贝 ?
开发语言·c++·算法