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

目录

一、引言

二、ReentrantLock 概述

2.1 可重入性

2.2 公平锁与非公平锁

三、ReentrantLock 的使用方法

3.1 基本使用

3.2 尝试获取锁

3.3 可中断的锁获取

四、ReentrantLock 的实现原理

五、ReentrantLock 与 synchronized 的对比

5.1 语法层面

5.2 功能层面

5.3 性能层面

六、使用 ReentrantLock 的注意事项

6.1 锁的释放

6.2 异常处理

6.3 公平锁的使用

七、总结


一、引言

在 Java 并发编程中,线程安全是一个核心问题。为了保证多个线程对共享资源的安全访问,需要使用同步机制。除了使用 synchronized 关键字外,Java 还提供了显式锁 ReentrantLockReentrantLock 是 java.util.concurrent.locks 包下的一个重要类,它提供了比 synchronized 更灵活、更强大的锁机制。本文将深入探讨 ReentrantLock 的原理、使用方法、优缺点以及与 synchronized 的对比。

二、ReentrantLock 概述

2.1 可重入性

ReentrantLock 是可重入锁,这意味着同一个线程可以多次获取同一把锁而不会造成死锁。每一次重入,锁的持有计数会加 1,每一次释放锁,持有计数会减 1,只有当持有计数为 0 时,锁才会真正被释放。例如:

import java.util.concurrent.locks.ReentrantLock;

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

    public void outerMethod() {
        lock.lock();
        try {
            System.out.println("Outer method locked");
            innerMethod();
        } finally {
            lock.unlock();
        }
    }

    public void innerMethod() {
        lock.lock();
        try {
            System.out.println("Inner method locked");
        } finally {
            lock.unlock();
        }
    }

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

在上述代码中,outerMethod 中获取了锁,然后调用 innerMethodinnerMethod 中又可以再次获取同一把锁,这就是可重入性的体现。

2.2 公平锁与非公平锁

ReentrantLock 可以创建公平锁和非公平锁。公平锁会按照线程请求锁的顺序依次获取锁,而非公平锁则不保证这一点,在锁释放时,任何等待的线程都有机会获取锁。创建公平锁的方式如下:

ReentrantLock fairLock = new ReentrantLock(true);

创建非公平锁的方式如下:

ReentrantLock nonFairLock = new ReentrantLock(false); // 或者直接使用无参构造函数 ReentrantLock nonFairLock = new ReentrantLock();

公平锁虽然保证了线程获取锁的顺序,但会带来一定的性能开销,因为需要维护一个有序的线程队列。非公平锁在大多数情况下性能更好,但可能会导致某些线程长时间得不到锁。

三、ReentrantLock 的使用方法

3.1 基本使用

使用 ReentrantLock 时,需要手动获取锁和释放锁,通常使用 try - finally 块来确保锁一定会被释放。示例代码如下:

import java.util.concurrent.locks.ReentrantLock;

public class BasicUsageExample {
    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 {
        BasicUsageExample example = new BasicUsageExample();
        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());
    }
}

3.2 尝试获取锁

ReentrantLock 提供了 tryLock() 方法,用于尝试获取锁。如果锁当前可用,则获取锁并返回 true;如果锁不可用,则立即返回 false,不会阻塞线程。示例代码如下:

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("Got the lock and doing something");
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("Couldn't get the lock, doing something else");
        }
    }

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

3.3 可中断的锁获取

ReentrantLock 还提供了 lockInterruptibly() 方法,允许线程在等待锁的过程中被中断。如果线程在等待锁的过程中被中断,则会抛出 InterruptedException 异常。示例代码如下:

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("Got the lock and starting work");
            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("Thread was interrupted");
            }
        });
        t.start();

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

四、ReentrantLock 的实现原理

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

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

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

五、ReentrantLock 与 synchronized 的对比

5.1 语法层面

  • synchronized 是 Java 的关键字,是隐式锁,使用起来更加简洁,不需要手动释放锁。
  • ReentrantLock 是一个类,需要手动调用 lock() 和 unlock() 方法来获取和释放锁。

5.2 功能层面

  • ReentrantLock 提供了更多的功能,如可中断的锁获取、尝试获取锁、公平锁等,而 synchronized 则相对固定,缺乏这些灵活的控制。
  • ReentrantLock 可以实现多个条件变量(Condition),可以更精细地控制线程的等待和唤醒。

5.3 性能层面

  • 在 JDK 早期,ReentrantLock 的性能通常优于 synchronized。但随着 JDK 的不断优化,synchronized 的性能已经有了很大的提升,在大多数情况下两者的性能差异不大。不过,在高并发场景下,ReentrantLock 的可定制性可能会带来更好的性能。

六、使用 ReentrantLock 的注意事项

6.1 锁的释放

由于 ReentrantLock 是显式锁,必须在 finally 块中调用 unlock() 方法来释放锁,以确保无论是否发生异常,锁都能被正确释放。如果忘记释放锁,可能会导致死锁或其他线程无法获取锁。

6.2 异常处理

在使用 lockInterruptibly() 方法时,需要处理 InterruptedException 异常,以确保线程在被中断时能够正确响应。

6.3 公平锁的使用

公平锁虽然保证了线程获取锁的顺序,但会带来一定的性能开销,因此在使用公平锁时需要权衡性能和公平性。

七、总结

ReentrantLock 是 Java 并发编程中一个强大的工具,它提供了比 synchronized 更灵活、更强大的锁机制。通过合理使用 ReentrantLock 的各种特性,如可重入性、公平锁、尝试获取锁、可中断的锁获取等,可以编写出更高效、更健壮的并发程序。同时,深入理解 ReentrantLock 的实现原理和与 synchronized 的对比,能够帮助我们在不同的场景下选择合适的同步机制。在实际开发中,需要根据具体的业务需求和性能要求,灵活运用 ReentrantLock 来保证线程安全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

潜意识Java

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

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

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

打赏作者

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

抵扣说明:

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

余额充值