深入理解 Java 的显式锁(ReentrantLock)

目录

引言

一、ReentrantLock 概述

(一)基本概念

(二)与 synchronized 的对比

二、ReentrantLock 的使用方法

(一)基本使用

(二)尝试获取锁

(三)可中断的锁获取

(四)公平锁

三、ReentrantLock 的实现原理

(一)基于 AQS(AbstractQueuedSynchronizer)

(二)状态变量

(三)线程队列

四、使用 ReentrantLock 的注意事项

(一)锁的释放

(二)避免死锁

(三)性能考虑

五、总结

引言

在 Java 并发编程中,线程同步是确保多线程程序正确性和一致性的关键。synchronized 关键字是 Java 中最常用的同步机制,但从 Java 5 开始,java.util.concurrent.locks 包中引入了 ReentrantLock 类,它作为一种显式锁,提供了比 synchronized 更强大、更灵活的同步功能。深入理解 ReentrantLock 的特性、使用方法和实现原理,对于编写高效、健壮的并发程序至关重要。

一、ReentrantLock 概述

(一)基本概念

ReentrantLock 是可重入的互斥锁,这意味着同一个线程可以多次获取该锁而不会产生死锁。“可重入” 特性允许线程在持有锁的情况下,再次进入被该锁保护的代码块,每一次重入都会使锁的持有计数加 1,而每次释放锁时,持有计数减 1,只有当持有计数为 0 时,锁才会真正被释放。

(二)与 synchronized 的对比

  1. 灵活性ReentrantLock 提供了更丰富的 API,例如可以尝试获取锁(tryLock())、可以设置获取锁的超时时间(tryLock(long timeout, TimeUnit unit))、可以实现公平锁(ReentrantLock(boolean fair) 构造函数传入 true)等,而 synchronized 则相对固定,缺乏这些灵活的控制。
  2. 锁的释放synchronized 是隐式锁,当代码块执行完毕或抛出异常时,锁会自动释放;而 ReentrantLock 是显式锁,需要在 finally 块中手动调用 unlock() 方法来释放锁,否则可能会导致锁无法释放,造成死锁。
  3. 锁的获取synchronized 无法中断正在等待获取锁的线程,而 ReentrantLock 可以通过 lockInterruptibly() 方法允许线程在等待锁的过程中被中断。

二、ReentrantLock 的使用方法

(一)基本使用

以下是一个简单的使用 ReentrantLock 实现线程同步的示例:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockExample example = new ReentrantLockExample();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("Count: " + example.getCount());
    }
}

在上述代码中,increment() 和 getCount() 方法使用 ReentrantLock 来保证线程安全,通过 lock.lock() 获取锁,在 finally 块中使用 lock.unlock() 释放锁。

(二)尝试获取锁

tryLock() 方法用于尝试获取锁,如果锁当前可用,则获取锁并返回 true;如果锁不可用,则立即返回 false,不会阻塞线程。以下是一个使用 tryLock() 的示例:

import java.util.concurrent.locks.ReentrantLock;

public class TryLockExample {
    private final ReentrantLock lock = new ReentrantLock();

    public void doSomething() {
        if (lock.tryLock()) {
            try {
                // 执行需要同步的操作
                System.out.println("获取到锁,执行操作");
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("未获取到锁,执行其他操作");
        }
    }

    public static void main(String[] args) {
        TryLockExample example = new TryLockExample();
        example.doSomething();
    }
}

(三)可中断的锁获取

lockInterruptibly() 方法允许线程在等待获取锁的过程中被中断。如果线程在等待锁的过程中被其他线程中断,则会抛出 InterruptedException 异常。以下是一个使用 lockInterruptibly() 的示例:

import java.util.concurrent.locks.ReentrantLock;

public class InterruptibleLockExample {
    private final ReentrantLock lock = new ReentrantLock();

    public void doWork() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            // 执行需要同步的操作
            System.out.println("获取到锁,开始工作");
            Thread.sleep(5000);
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        InterruptibleLockExample example = new InterruptibleLockExample();
        Thread t = new Thread(() -> {
            try {
                example.doWork();
            } catch (InterruptedException e) {
                System.out.println("线程被中断");
            }
        });
        t.start();

        try {
            Thread.sleep(2000);
            t.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

(四)公平锁

ReentrantLock 可以通过构造函数 ReentrantLock(boolean fair) 创建公平锁,当 fair 参数为 true 时,表示创建公平锁。公平锁会按照线程请求锁的顺序依次获取锁,避免某些线程长时间得不到锁的情况。但公平锁的性能相对较低,因为需要维护一个有序的线程队列。以下是一个使用公平锁的示例:

import java.util.concurrent.locks.ReentrantLock;

public class FairLockExample {
    private final ReentrantLock fairLock = new ReentrantLock(true);

    public void doSomething() {
        fairLock.lock();
        try {
            // 执行需要同步的操作
            System.out.println(Thread.currentThread().getName() + " 获取到公平锁");
        } finally {
            fairLock.unlock();
        }
    }

    public static void main(String[] args) {
        FairLockExample example = new FairLockExample();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> example.doSomething()).start();
        }
    }
}

三、ReentrantLock 的实现原理

(一)基于 AQS(AbstractQueuedSynchronizer)

ReentrantLock 的实现依赖于 Java 中的 AQS(AbstractQueuedSynchronizer),AQS 是一个用于构建锁和同步器的框架,它提供了一个基于 FIFO 队列的同步机制。ReentrantLock 通过继承 AQS 并实现其相关方法来实现锁的功能。

(二)状态变量

AQS 内部维护了一个 int 类型的状态变量 state,用于表示锁的状态。对于 ReentrantLock,当 state 为 0 时,表示锁未被任何线程持有;当 state 大于 0 时,表示锁已经被某个线程持有,并且 state 的值表示该线程重入锁的次数。

(三)线程队列

AQS 还维护了一个 FIFO 的线程队列,当线程请求锁但锁不可用时,线程会被加入到该队列中等待。当锁被释放时,队列中的第一个线程会被唤醒并尝试获取锁。

四、使用 ReentrantLock 的注意事项

(一)锁的释放

由于 ReentrantLock 是显式锁,必须在 finally 块中调用 unlock() 方法来释放锁,以确保无论是否发生异常,锁都能被正确释放。

(二)避免死锁

在使用 ReentrantLock 时,要注意避免死锁的发生。例如,不要在持有一个锁的情况下尝试获取另一个锁,或者在多个线程中以不同的顺序获取锁。

(三)性能考虑

虽然 ReentrantLock 提供了更强大的功能,但在一些简单的同步场景中,使用 synchronized 可能会更简单、性能更好。因为 synchronized 是 JVM 内置的同步机制,经过了大量的优化。

五、总结

ReentrantLock 作为 Java 中的显式锁,提供了比 synchronized 更灵活、更强大的同步功能。通过合理使用 ReentrantLock 的各种特性,如尝试获取锁、可中断的锁获取和公平锁等,可以编写出更高效、更健壮的并发程序。同时,深入理解 ReentrantLock 的实现原理和使用注意事项,能够帮助我们更好地应对并发编程中的各种挑战。在实际开发中,应根据具体的业务场景和需求,选择合适的同步机制。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

潜意识Java

源码一定要私信我,有问题直接问

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

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

打赏作者

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

抵扣说明:

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

余额充值