Java基础教程(142)线程同步之使用ReentrantLock:告别synchronized!深度剖析Java高并发利器ReentrantLock

深度分析Java线程同步之使用ReentrantLock

在Java并发编程中,线程同步是确保数据一致性和线程安全的基石。虽然内置的 synchronized 关键字简单易用,但其局限性(如无法中断、非公平、单一条件队列)在复杂的应用场景中逐渐显现。java.util.concurrent.locks.ReentrantLock 作为JUC包中的佼佼者,提供了一种更灵活、更强大的互斥锁实现。

一、ReentrantLock核心优势

synchronized 相比,ReentrantLock 带来了多项关键提升:

  1. 可重入性:与 synchronized 一样,一个线程可以多次获取同一把锁,避免了死锁。
  2. 尝试非阻塞获取锁:tryLock() 方法允许线程尝试获取锁,如果失败立即返回或等待指定时间,而非一直阻塞,极大地提高了程序的灵活性。
  3. 可中断的锁获取:lockInterruptibly() 方法允许在等待锁的过程中响应中断,为处理线程终止提供了更优雅的方式。
  4. 公平锁与非公平锁:构造函数允许选择创建公平锁或非公平锁。公平锁遵循FIFO原则,减少了线程饥饿;非公平锁则提供更高的吞吐量,默认是非公平的。
  5. 丰富的条件变量(Condition):一个 ReentrantLock 可以关联多个 Condition 对象,实现更精细的线程等待/通知机制,远超 synchronizedwait()/notifyAll()
二、synchronized vs. ReentrantLock:何时选择?
  • synchronized:语法简洁,由JVM自动释放锁,不易出错。适用于简单的、锁竞争不激烈的同步块。
  • ReentrantLock:当需要上述高级功能(如可中断、超时、公平性、多个条件)时,或synchronized成为性能瓶颈时,应毫不犹豫地选择它。代价是必须在 finally 块中手动释放锁,否则会导致严重后果。
三、实战代码示例

下面通过一个经典的“银行账户”示例,展示 ReentrantLock 的基本用法和高级特性。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;

public class BankAccount {
    private final Lock lock = new ReentrantLock();
    // 创建一个与lock绑定的条件变量,用于表示“账户有足够余额”
    private final Condition sufficientFunds = lock.newCondition();
    private long balance;

    public BankAccount(long balance) {
        this.balance = balance;
    }

    // 基础用法:使用lock()和unlock()
    public void withdrawBasic(long amount) {
        lock.lock(); // 获取锁
        try {
            while (balance < amount) {
                // 使用synchronized时,这里只能wait()
                sufficientFunds.await(); // 释放锁并等待,被唤醒后会自动重新获取锁
            }
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + " 取款 " + amount + ",余额: " + balance);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // 必须在finally中确保锁被释放
        }
    }

    // 高级用法:尝试获取锁(tryLock)和可中断
    public boolean tryTransfer(BankAccount to, long amount, long timeout) throws InterruptedException {
        long stopTime = System.nanoTime() + timeout;
        while (true) {
            if (this.lock.tryLock()) { // 尝试获取本账户的锁
                try {
                    if (to.lock.tryLock()) { // 尝试获取目标账户的锁
                        try {
                            if (balance >= amount) {
                                balance -= amount;
                                to.balance += amount;
                                System.out.println("转账成功!金额: " + amount);
                                return true;
                            } else {
                                return false; // 余额不足
                            }
                        } finally {
                            to.lock.unlock();
                        }
                    }
                } finally {
                    this.lock.unlock();
                }
            }
            if (System.nanoTime() > stopTime) {
                return false; // 超时失败
            }
            // 短暂休眠,避免活锁
            Thread.sleep(10);
        }
    }

    public void deposit(long amount) {
        lock.lock();
        try {
            balance += amount;
            System.out.println(Thread.currentThread().getName() + " 存款 " + amount + ",余额: " + balance);
            // 存款后,通知所有等待“sufficientFunds”条件的线程
            sufficientFunds.signalAll();
        } finally {
            lock.unlock();
        }
    }
}
四、总结

ReentrantLock 将线程同步的控制权从JVM手中交还给了开发者。它通过显式的API调用,让我们能够编写出更适应复杂业务逻辑、更具弹性和鲁棒性的并发程序。虽然需要小心处理锁的释放以避免死锁,但其带来的强大功能使得这一点微不足道。在面对高性能、高并发的现代应用架构时,熟练掌握 ReentrantLock 是每一位Java开发者的必备技能。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

值引力

持续创作,多谢支持!

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

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

打赏作者

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

抵扣说明:

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

余额充值