如何选择合适的锁机制来提高 Java 程序的性能?

一、选锁的核心原则(先明确这 3 点)

  1. 先无锁,再轻锁,最后重锁:能不用锁就不用(如原子类),能用轻量级锁就不用重量级锁,减少锁带来的性能开销。
  2. 匹配读写比例:多读少写、多写少读、读写均衡,对应不同的锁策略。
  3. 控制锁粒度:锁的范围越小(如只锁代码块而非整个方法)、锁的对象越具体(如分段锁),性能越好。

二、分场景的锁选择方案(附性能对比)

场景 1:无竞争 / 极低竞争(如单线程、偶尔并发)
  • 推荐选择:无锁(Atomic 原子类) > 偏向锁(synchronized 自动优化)

  • 核心原因:无锁通过 CAS 实现,无上下文切换开销;synchronized 会自动升级为偏向锁,几乎无性能损耗。

  • 代码示例(AtomicInteger 无锁)

    java

    运行

    复制代码
    // 无锁,CAS 原子操作,适合低竞争计数场景
    AtomicInteger count = new AtomicInteger(0);
    // 原子自增,无需加锁,性能远高于 synchronized
    count.incrementAndGet();
  • 性能优势:无锁 > 偏向锁 > 显式锁,无上下文切换、无锁竞争开销。

场景 2:多读少写(如缓存、配置读取、数据查询)
  • 推荐选择:乐观锁(CAS) > 读写锁(ReentrantReadWriteLock) > StampedLock(JDK 8+)

  • 核心原因

    • 读写锁:读锁共享(多个读线程并行),写锁排他,解决 "读多写少" 的锁竞争问题;
    • StampedLock:支持 "乐观读"(无需加锁,仅验证版本),性能比读写锁更高(但使用稍复杂);
    • 乐观锁:适合几乎无写操作的场景,完全无锁竞争。
  • 性能对比:StampedLock(乐观读) > 读写锁 > synchronized/ReentrantLock

  • 代码示例(StampedLock 乐观读)

    java

    运行

    复制代码
    import java.util.concurrent.locks.StampedLock;
    
    public class StampedLockDemo {
        private final StampedLock lock = new StampedLock();
        private int value = 0;
    
        // 乐观读(核心优化点)
        public int getValue() {
            // 1. 获取乐观读戳记(无锁)
            long stamp = lock.tryOptimisticRead();
            int currentValue = value;
            // 2. 验证读期间是否有写操作(无写则直接返回,有写则升级为读锁)
            if (!lock.validate(stamp)) {
                // 升级为悲观读锁
                stamp = lock.readLock();
                try {
                    currentValue = value;
                } finally {
                    lock.unlockRead(stamp);
                }
            }
            return currentValue;
        }
    
        // 写操作(排他锁)
        public void setValue(int newValue) {
            long stamp = lock.writeLock();
            try {
                value = newValue;
            } finally {
                lock.unlockWrite(stamp);
            }
        }
    }
场景 3:多写少读(如订单创建、数据入库)
  • 推荐选择:非公平锁(ReentrantLock 默认) > synchronized(重量级锁) > 公平锁
  • 核心原因
    • 非公平锁:无需排队,线程抢锁成功率更高,减少上下文切换,性能比公平锁高 10%-20%;
    • synchronized:JDK 1.6 后优化(轻量级锁 / 偏向锁),性能接近 ReentrantLock,但灵活性低;
    • 公平锁:严格按顺序排队,避免线程饥饿,但性能低,仅适合需保证顺序的场景。
  • 性能对比:非公平 ReentrantLock > synchronized > 公平 ReentrantLock
  • 关键优化:缩小锁粒度(如只锁写入逻辑,而非整个方法)。
场景 4:高并发且读写均衡(如秒杀、库存扣减)
  • 推荐选择:分段锁(ConcurrentHashMap) > 自旋锁 > 分布式锁(Redis/ZooKeeper)

  • 核心原因

    • 分段锁:将锁拆分为多个小锁(如 ConcurrentHashMap 把数据分成 16 段,每段独立加锁),降低锁竞争;
    • 自旋锁:线程抢锁失败时不阻塞,而是循环重试(适合短时间锁持有),减少上下文切换;
    • 分布式锁:跨 JVM 并发时使用,优先选 Redis(性能高),其次 ZooKeeper(可靠性高)。
  • 代码示例(分段锁思想)

    java

    运行

    复制代码
    import java.util.concurrent.locks.ReentrantLock;
    
    // 模拟分段锁:将库存按商品ID分段,每段独立加锁
    public class SegmentLockDemo {
        // 分段数(一般设为 CPU 核心数*2)
        private static final int SEGMENT_COUNT = 16;
        private final ReentrantLock[] locks = new ReentrantLock[SEGMENT_COUNT];
        private final int[] stock = new int[SEGMENT_COUNT];
    
        public SegmentLockDemo() {
            // 初始化分段锁
            for (int i = 0; i < SEGMENT_COUNT; i++) {
                locks[i] = new ReentrantLock();
                stock[i] = 1000; // 每段初始库存
            }
        }
    
        // 扣减库存:只锁对应分段
        public void deductStock(int goodsId) {
            // 计算商品ID对应的分段
            int segment = Math.abs(goodsId % SEGMENT_COUNT);
            ReentrantLock lock = locks[segment];
            lock.lock();
            try {
                if (stock[segment] > 0) {
                    stock[segment]--;
                    System.out.println("商品" + goodsId + "库存扣减后:" + stock[segment]);
                }
            } finally {
                lock.unlock();
            }
        }
    }

三、性能优化的关键技巧

  1. 避免锁膨胀:synchronized 偏向锁→轻量级锁→重量级锁升级后不可逆,高并发下尽量减少锁竞争(如拆分锁、无锁设计)。
  2. 减少锁持有时间:仅在核心临界区加锁(如读取数据不加锁,仅写入加锁)。
  3. 使用并发容器替代手动加锁:优先用 ConcurrentHashMap、CopyOnWriteArrayList 等,底层已做锁优化。
  4. 避免死锁:按固定顺序加锁、设置锁超时(ReentrantLock 的 tryLock (超时时间))。

总结

  1. 先判断并发特征:读多写少选读写锁 / StampedLock,多写少读选非公平 ReentrantLock,高并发选分段锁;
  2. 优先无锁 / 轻锁:低竞争用 Atomic 原子类,避免无脑用 synchronized;
  3. 控制锁粒度和持有时间:这是提升锁性能最有效的手段,比选择锁类型更重要。
相关推荐
A懿轩A21 小时前
【Java 基础编程】Java 变量与八大基本数据类型详解:从声明到类型转换,零基础也能看懂
java·开发语言·python
web打印社区21 小时前
web-print-pdf:突破浏览器限制,实现专业级Web静默打印
前端·javascript·vue.js·electron·html
m0_7400437321 小时前
【无标题】
java·spring boot·spring·spring cloud·微服务
Tansmjs1 天前
使用Python自动收发邮件
jvm·数据库·python
m0_561359671 天前
用Python监控系统日志并发送警报
jvm·数据库·python
RFCEO1 天前
前端编程 课程十三、:CSS核心基础1:CSS选择器
前端·css·css基础选择器详细教程·css类选择器使用方法·css类选择器命名规范·css后代选择器·精准选中嵌套元素
@ chen1 天前
Spring事务 核心知识
java·后端·spring
idwangzhen1 天前
GEO优化系统哪个功能强大
python·信息可视化
aithinker1 天前
使用QQ邮箱收发邮件遇到的坑 有些WIFI不支持ipv6
java
Amumu121381 天前
Vuex介绍
前端·javascript·vue.js