【007】Java中synchronized和ReentrantLock有什么区别?


Java中synchronized和ReentrantLock的区别主要体现在以下几个方面:

1、锁的实现方式: synchronized是Java内置的关键字,JVM层面实现;ReentrantLock是Java类库中的一个API。

2、锁的公平性: synchronized不保证公平性;而ReentrantLock可以通过构造函数设置公平性。

3、锁的灵活性: ReentrantLock提供更灵活的锁操作,它支持中断锁的获取、超时获取锁等高级功能。

4、条件变量支持: ReentrantLock可以与Condition类配合,实现分组唤醒等复杂的线程协作。


synchronized和ReentrantLock的区别详解

synchronized vs ReentrantLock:Java 锁界的 “躺平大爷” vs “内卷精英”:

如果说 Java 并发是 “线程过马路”,那synchronized和ReentrantLock就是俩 “交通管理员”—— 一个是按规矩办事、啥也不多管的退休大爷,一个是技能拉满、灵活应变的年轻精英。俩都能搞定 “线程不抢道”,但用法、能力、适用场景天差地别,今天就用 “人话 + 热梗” 扒得明明白白!

一、先定人设:俩锁的核心气质差异

锁类型人设标签核心气质
synchronized退休居委会大爷体制内出身(JVM 内置),规则简单、自动干活,不用你操心,但认死理、功能单一
ReentrantLock互联网内卷精英第三方创业(Java 代码实现),手动操作、技能点满,灵活可控,但需要自己操心细节

简单说:synchronized是 “全自动洗衣机”,按下开关就不管;ReentrantLock是 “手动挡跑车”,能漂移能加速,但得自己挂挡踩油门!

二、使用方式:一个 “躺平式”,一个 “内卷式”

2.1 synchronized:大爷帮你 “包办一切”

用synchronized就像过小区门禁,你只要走到门口(进入同步方法 / 代码块),大爷自动锁门;你走了(方法 / 代码块结束),大爷自动开门,全程 “零手动操作”,懒癌患者福音。

🚀 代码示例(3 种用法,全是自动档)

// 1. 同步方法:相当于“进大门,大爷直接拦着别人”
public synchronized void goHome() {
    System.out.println("大爷守门:我进家了,其他人排队!");
}

// 2. 同步代码块:相当于“进客厅,大爷只拦客厅门”
public void watchTV() {
    synchronized (this) { // this = 客厅门钥匙
        System.out.println("大爷守门:我占客厅看电视,别人别来抢遥控器!");
    }
}

// 3. 同步静态方法:相当于“进小区公共健身房,大爷拦健身房门”
public static synchronized void goGym() {
    System.out.println("大爷守门:健身房被我占了,等我练完!");
}

核心特点: 语法极简,“自动加锁 + 自动释放”,哪怕代码抛异常,大爷也会乖乖开门(JVM 自动释放锁),绝不会出现 “锁没关” 的情况。

2.2 ReentrantLock:精英让你 “亲力亲为”

用ReentrantLock就像用智能门锁,你得自己刷卡(lock())进门,出门必须手动锁门(unlock()),还得把 “锁门” 这事刻进 DNA—— 忘了锁门,就等于 “敞着门睡觉”(死锁风险)。

✔️ 代码示例(手动挡,一步都不能错)

import java.util.concurrent.locks.ReentrantLock;

public class LockDemo {
    // 初始化智能门锁(可传true开启“公平模式”,后面讲)
    private final ReentrantLock lock = new ReentrantLock();

    public void goHome() {
        lock.lock(); // 刷卡进门(手动加锁)
        try {
            System.out.println("精英管家:我进家了,其他人排队!");
        } finally {
            // 必须放finally!就算抛异常,也得锁门
            lock.unlock(); // 手动锁门(释放锁)
        }
    }

    // 进阶用法:尝试进门,5秒没进去就走(避免死等)
    public void tryGoHome() throws InterruptedException {
        // tryLock(5秒):最多等5秒,拿不到锁就返回false
        if (lock.tryLock(5, java.util.concurrent.TimeUnit.SECONDS)) {
            try {
                System.out.println("精英管家:抢上门了!");
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("精英管家:等5秒还没抢到,先去喝杯奶茶!");
        }
    }
}

核心特点:语法繁琐,但可控性拉满。能实现synchronized做不到的 “骚操作”(超时等待、可中断、公平锁),但必须手动释放锁,否则会导致 “锁泄漏”(其他线程永远拿不到锁)。

三、核心差异:一张表怼穿 “大爷” 和 “精英” 的差距

在这里插入图片描述

四、ReentrantLock 的 “骚操作”

4.1 公平锁:拒绝 “插队”,实现 “排队自由”

synchronized默认是 “非公平锁”—— 线程 A 刚释放锁,线程 C 就从旁边冲进来插队,比排队很久的线程 B 先拿到锁(大爷不管排队,谁猛谁上)

ReentrantLock可以通过构造函数开启 “公平锁”:

// 开启公平锁:线程必须按请求顺序拿锁,不允许加塞
private final ReentrantLock fairLock = new ReentrantLock(true);

适用场景:银行办理业务、秒杀活动排队等,需要 “先来后到” 的场景,避免线程饥饿(某些线程永远抢不到锁)。

4.2 可中断锁:等不及就 “溜”,不做 “死等冤种”

用synchronized时,线程 A 持有锁,线程 B 想拿锁只能死等 —— 就算等 1 小时,也不能中途放弃(除非线程被杀死),典型的 “冤种行为”。

ReentrantLock的lockInterruptibly()支持 “可中断”:

public void interruptibleLock() throws InterruptedException {
    lock.lockInterruptibly(); // 等待时可以被中断
    try {
        // 临界区代码
    } finally {
        lock.unlock();
    }
}

// 其他线程可以调用 thread.interrupt() 打断等待中的线程

适用场景:比如用户发起一个请求,超过 3 秒没响应就取消,这时可以中断等待锁的线程,避免资源浪费。

4.3 多个条件变量:分场景等待,不 “一刀切”

synchronized只有一个条件队列,用wait()唤醒时,会唤醒所有等待的线程(notifyAll()),哪怕有些线程等的条件还没满足(比如线程 A 等 “饭熟”,线程 B 等 “菜好”,唤醒后俩线程都得跑过来检查)。

ReentrantLock的Condition可以创建多个条件队列,精准唤醒:

import java.util.concurrent.locks.Condition;

public class ConditionDemo {
    private final ReentrantLock lock = new ReentrantLock();
    // 条件1:饭熟了
    private final Condition riceReady = lock.newCondition();
    // 条件2:菜好了
    private final Condition dishReady = lock.newCondition();

    // 等饭的线程
    public void waitForRice() throws InterruptedException {
        lock.lock();
        try {
            riceReady.await(); // 只等“饭熟”的信号,不关心“菜”
            System.out.println("饭熟了,开始吃!");
        } finally {
            lock.unlock();
        }
    }

    // 做饭的线程:饭熟了,只唤醒等饭的线程
    public void cookRice() {
        lock.lock();
        try {
            System.out.println("饭做好了!");
            riceReady.signal(); // 精准唤醒等饭的线程,不打扰等菜的
        } finally {
            lock.unlock();
        }
    }
}

适用场景:生产者 - 消费者模型(比如多个生产者、多个消费者,精准唤醒对应角色)。

五、避坑指南:别被俩锁 “坑死”!

5.1 ReentrantLock的最大坑:忘了释放锁!

如果没把unlock()放在finally里,一旦临界区代码抛异常,锁就永远不会释放,其他线程全堵死 —— 相当于 “你出门没锁门,结果家里被搬空了”。

// 错误示范:没放finally,抛异常就gg
public void badGoHome() {
    lock.lock();
    int i = 1 / 0; // 抛异常!
    lock.unlock(); // 这行永远执行不到,锁泄漏了!
}

// 正确示范:finally兜底,必释放锁
public void goodGoHome() {
    lock.lock();
    try {
        int i = 1 / 0;
    } finally {
        lock.unlock(); // 就算抛异常,也会执行!
    }
}

5.2 synchronized的坑:死等没商量!

如果一个线程持有synchronized锁,并且进入死循环(比如while(true)),其他线程想拿锁只能一直等,没有任何 “退出机制”—— 相当于 “大爷把门锁了,自己在里面睡大觉,外面的人永远进不来”。
这时ReentrantLock的tryLock()就能救场:“等 5 秒还没进去,就放弃”,避免线程无限等待。

5.3 公平锁别乱用:性能会下降!

ReentrantLock的公平锁虽然 “公平”,但需要维护一个排队队列,线程切换开销大 —— 相当于 “精英管家要一个个登记排队的人,效率变低”。非公平锁虽然可能 “插队”,但性能更高,大部分场景下非公平锁就够了。

六、选择建议:什么时候找大爷,什么时候找精英?

选synchronized(大爷)的场景:

  • 简单同步场景:比如给对象字段加 1、判断变量是否为空,代码逻辑简单。
  • 怕麻烦、不想写try-finally:担心自己忘了释放锁,交给 JVM 自动管理更放心。
  • 低并发场景:比如日常开发中的简单同步,没有复杂需求(超时、中断、公平锁)。

一句话:复杂大活找精英,功能强大能搞定!

七、总结:俩锁的 “终极定位”

  • synchronized:Java 并发的 “基础款锁”,语法简洁、安全可靠,是 “躺平式” 解决方案,覆盖 80% 的日常场景。
  • ReentrantLock:Java 并发的 “进阶款锁”,功能强大、灵活可控,是 “内卷式” 解决方案,用于解决 20% 的复杂场景。

🎲synchronized是 “佛系锁”,能搞定就行,不多操心;
🚀 ReentrantLock是 “卷王锁”,不仅要搞定,还要搞定得漂亮、精准、高效!实际开发中,先看场景,再选锁 —— 简单活别 “杀鸡用牛刀”,复杂活别 “用菜刀砍钢筋”!

请添加图片描述
请添加图片描述
学习快乐!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值