Java 锁怎么选?synchronized 还是 ReentrantLock?看场景就够了

咱们用「生活例子」+「通俗语言」重新讲,不搞复杂概念,只抓核心逻辑~

先记住一个核心:锁的作用就是 “抢资源”—— 让多个线程(比如多个人)按规则用同一个共享资源(比如打印机、厕所),别乱套

一、先搞懂 3 个基础概念(类比生活)

  1. 共享资源:多个人要抢着用的东西(比如公司打印机、公共厕所、代码里的共享变量)。
  2. 临界区:用共享资源的 “过程”(比如用打印机打印文件、用厕所的时间、代码里操作共享变量的几行代码)。
  3. :控制谁能进临界区的 “规则”(比如打印机前排队、厕所门的锁、代码里的同步机制)。

二、Java 里的锁,按 “好懂程度” 排序

1. 最傻瓜的锁:synchronized(隐式锁)

相当于「厕所门的自动锁」—— 进去后门自动锁,出来后自动开,不用你手动操作,简单粗暴。

怎么用?(3 种场景,类比理解)
  • 场景 1:修饰实例方法 → 锁 “当前对象”(比如你占了公司的某个打印机,别人不能用这台)。
    public class Printer {
        // 锁的是 Printer 的实例(比如 printer1、printer2 是不同锁)
        public synchronized void print() {
            System.out.println("打印中..."); // 临界区(用打印机的过程)
        }
    }
    
  • 场景 2:修饰静态方法 → 锁 “整个类”(比如公司所有打印机都被你占了,别人一台都不能用)。
    public class Printer {
        // 锁的是 Printer 这个类(所有实例共用一把锁)
        public static synchronized void printAll() {
            System.out.println("所有打印机都被我用了...");
        }
    }
    
  • 场景 3:修饰代码块 → 锁 “自定义对象”(比如你只占打印机的 “复印功能”,别人还能用来打印)。
    public class Printer {
        private final Object copyLock = new Object(); // 专门管“复印”的锁
        
        public void copy() {
            synchronized (copyLock) { // 只锁复印功能
                System.out.println("复印中...");
            }
        }
        
        public void print() { // 打印功能不受影响,别人能同时用
            System.out.println("打印中...");
        }
    }
    
核心特点:
  • 不用手动开锁 / 关锁(JVM 帮你弄),新手闭眼用。
  • 别人用的时候,你只能排队等(非公平,可能有人插队,效率高)。
  • 你自己可以反复进(比如你在厕所里,还能再开一次锁,不会卡住)→ 可重入。

2. 更灵活的锁:ReentrantLock(显式锁)

相当于「手动开的保险柜」—— 要自己用钥匙开锁(lock()),用完必须手动关锁(unlock()),但功能更多(比如可以设置 “排队规则”、“超时放弃”)。

核心优势(解决 synchronized 的痛点):
  • synchronized 的问题:你排队等锁时,不能中途放弃,也不能被人叫走(不可中断);而 ReentrantLock 可以。
  • 举个生活例子:你排队用保险柜,等了 3 分钟还没轮到,你可以选择放弃(超时);或者朋友叫你有急事,你可以中途离开(可中断)。
简单代码示例(一看就懂):
import java.util.concurrent.locks.ReentrantLock;

public class SafeBox {
    // 初始化锁:true=公平锁(排队按顺序),false=非公平锁(默认,可能插队)
    private final ReentrantLock lock = new ReentrantLock(true);

    public void getMoney() {
        try {
            // 尝试开锁,最多等3秒(超时就放弃)
            boolean gotLock = lock.tryLock(3, java.util.concurrent.TimeUnit.SECONDS);
            if (gotLock) {
                System.out.println("打开保险柜,取钱成功!"); // 临界区
            } else {
                System.out.println("等了3秒还没轮到,放弃取钱~");
            }
        } catch (InterruptedException e) {
            System.out.println("中途被打断(比如有人叫我),放弃取钱~");
        } finally {
            // 必须手动关锁!(不然别人永远用不了)
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}
什么时候用?
  • 想控制 “排队规则”(比如必须按顺序,不能插队 → 公平锁)。
  • 不想死等(比如等 5 秒没拿到锁就放弃)。
  • 中途可能需要中断(比如线程被唤醒去做别的事)。

3. 读多写少专用:读写锁(ReentrantReadWriteLock)

相当于「图书馆的书架」——

  • 读操作(看书):多个人可以同时看(共享锁),不冲突。
  • 写操作(修改书的内容):只能一个人改(排他锁),别人不能读也不能写。
生活场景:

图书馆里,10 个人可以同时看同一本书(读锁共享);但如果有人要修改这本书的内容(写锁),必须等所有人都看完,他一个人改完后,别人才能再看。

简单代码示例:
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Library {
    // 读写锁:读锁和写锁是一对
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock(); // 读锁
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock(); // 写锁
    private String bookContent = "初始内容"; // 共享资源(书的内容)

    // 读操作(多人可同时读)
    public String readBook() {
        readLock.lock(); // 加读锁
        try {
            System.out.println("有人在看书,内容:" + bookContent);
            return bookContent;
        } finally {
            readLock.unlock(); // 释放读锁
        }
    }

    // 写操作(只能一人写)
    public void writeBook(String newContent) {
        writeLock.lock(); // 加写锁
        try {
            bookContent = newContent;
            System.out.println("有人修改了书的内容:" + newContent);
        } finally {
            writeLock.unlock(); // 释放写锁
        }
    }
}
什么时候用?
  • 读操作特别多,写操作特别少(比如缓存、配置文件读取)。
  • 比如:1000 个线程读数据,1 个线程更新数据 → 用读写锁比 synchronized 快 100 倍(因为读不用排队)。

4. 无锁方案:volatile + 原子类(不用抢锁,更高效)

有些场景不用 “锁”,也能保证安全,相当于「不用排队的共享资源」。

(1)volatile:简单的 “状态通知”

相当于「公司的公告栏」—— 有人更新了公告(写线程改变量),所有人都能立刻看到(读线程拿到最新值),但不能保证 “同时改” 的安全。

适用场景:

只有一个人写,多个人读(比如 “是否停止” 的标记)。

public class NoticeBoard {
    // volatile 保证:写了之后,所有人都能立刻看到
    private volatile boolean isStop = false;

    // 一个人写(更新公告)
    public void setStop() {
        isStop = true;
        System.out.println("公告:停止工作!");
    }

    // 多个人读(看公告)
    public void work() {
        while (!isStop) { // 每次都能拿到最新的 isStop 值
            System.out.println("正在工作...");
        }
    }
}
(2)原子类(AtomicInteger 等):简单的 “计数”

相当于「自动计数的投票箱」—— 多人同时投票(加 1),不会数错,不用排队(底层用 CAS 机制,相当于 “先看有没有人改,没人改再自己改”)。

适用场景:

简单的加减计数(比如统计访问量)。

import java.util.concurrent.atomic.AtomicInteger;

public class VoteBox {
    // 原子类:自带“原子操作”,不用锁
    private final AtomicInteger voteCount = new AtomicInteger(0);

    // 多人同时投票(count++),不会数错
    public void vote() {
        voteCount.incrementAndGet(); // 相当于 count++,但线程安全
        System.out.println("当前票数:" + voteCount.get());
    }
}

三、怎么选锁?(一句话总结)

场景(生活类比)选什么锁 / 机制一句话理由
简单抢资源(比如抢打印机)synchronized不用手动开关,新手首选
想排队按顺序 / 不想死等 / 要中断ReentrantLock灵活,能解决 synchronized 的痛点
读多写少(比如图书馆看书)读写锁 / StampedLock读不用排队,效率高
单写多读(比如看公告)volatile不用锁,简单高效
简单计数(比如投票)原子类(AtomicInteger)不用锁,不会数错

四、避坑小技巧(新手必看)

  1. 用 ReentrantLock 一定要在finally里关锁(不然你忘了关,别人永远用不了)。
  2. 别嵌套锁(比如先锁 A 再锁 B,又先锁 B 再锁 A)→ 容易造成死锁(两个人互相拿着对方要的钥匙,都动不了)。
  3. 锁的范围越小越好(比如只锁 “复印” 功能,别把整个打印机都锁了)→ 别人还能同时用其他功能,效率高。

这样是不是好懂多了?核心就是「根据场景选规则」,不用死记概念,记住生活类比就行~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

canjun_wen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值