ReentrantLock、ReentrantReadWriteLock、StampedLock讲解

文章目录

  • ReentrantLock、ReentrantReadWriteLock、StampedLock讲解
    • 1. 概述
    • 2. ReentrantLock
      • 2.1 基本概念
      • 2.2 使用方法
      • 2.3 特性
      • 2.4 总结
    • 3. ReentrantReadWriteLock
      • 3.1 基本概念
      • 3.2 使用方法
      • 3.3 特性
      • 3.4 总结
    • 4. StampedLock
      • 4.1 基本概念
      • 4.2 使用方法

ReentrantLock、ReentrantReadWriteLock、StampedLock讲解

1. 概述

在多线程编程中,锁是必不可少的,因为许多并发问题都可以通过加锁来解决,例如线程安全的单例实现、线程安全的数据结构实现等等。Java提供了许多类型的锁,本文就来介绍其中三种常见的锁:ReentrantLock、ReentrantReadWriteLock、StampedLock。

2. ReentrantLock

2.1 基本概念

ReentrantLock(重入锁)是Java中的一个独占锁,也是一种可重入锁。独占锁是指同一时刻只能有一个线程获得锁,其他线程必须等待;可重入锁是指线程可多次获取同一把锁,不会自己阻塞自己。

2.2 使用方法

使用ReentrantLock的基本方法如下:

ReentrantLock lock = new ReentrantLock(); // 创建锁对象
lock.lock(); // 获取锁
try {
    // 执行需要加锁的代码
} finally {
    lock.unlock(); // 释放锁
}

在上面的代码中,首先创建一个ReentrantLock对象,然后在需要加锁的代码执行前调用lock()方法获取锁,在需要释放锁的地方使用unlock()方法释放锁。

需要注意的是,在使用ReentrantLock时,要保证锁的获取和释放要在try…finally块中进行。因为在试图获取锁的过程中,如果线程被中断(interrupt),那么当前线程也需要释放锁资源。

2.3 特性

ReentrantLock有一些特性:

  • 重入性:同一个线程可以反复获取同一把锁,不会造成死锁。
  • 公平锁和非公平锁:ReentrantLock提供了两种获取锁的方式:公平锁和非公平锁。公平锁是指等待时间最长的线程优先获取锁,而非公平锁是指线程直接尝试获取锁,如果失败了才会进入等待队列。默认情况下,ReentrantLock是非公平锁。
  • 可中断:ReentrantLock提供了可中断的获取锁的方式,即在等待锁的过程中,如果线程被中断,则会抛出InterruptedException异常。在使用可中断获取锁的方式时,需要捕获InterruptedException异常。
  • 条件变量:ReentrantLock中可以使用Condition对象来实现线程间的通信与同步。Condition对象和锁对象是绑定在一起的,一个锁对象可以创建多个Condition对象,用于不同的等待队列。常见的方法有await()、signal()和signalAll()。

2.4 总结

ReentrantLock是一种可重入锁,提供了公平锁和非公平锁、可中断获取锁和条件变量等特性,使用起来比较灵活,但也比较复杂,需要手动控制加锁和释放锁的过程。

3. ReentrantReadWriteLock

3.1 基本概念

ReentrantReadWriteLock(可重入读写锁)是Java中另一种常用的锁,也是一种可重入锁。与ReentrantLock不同的是,ReentrantReadWriteLock既可以支持独占锁,也可以支持共享锁。共享锁是指同一时刻可以有多个线程读取共享资源,但只能有一个线程写入共享资源;独占锁是指同一时刻只能有一个线程获得锁,其他线程必须等待。因此,ReentrantReadWriteLock适用于读多写少的场景,可以提高并发读操作的效率。

3.2 使用方法

使用ReentrantReadWriteLock的基本方法如下:

ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); // 创建锁对象
lock.readLock().lock(); // 获取读锁
try {
    // 执行需要读取共享资源的代码
} finally {
    lock.readLock().unlock(); // 释放读锁
}

在上面的代码中,首先创建一个ReentrantReadWriteLock对象,然后在需要读取共享资源的代码执行前调用readLock().lock()方法获取读锁,在需要释放锁的地方使用readLock().unlock()方法释放读锁。

如果需要写入共享资源,则需要获取写锁,相应地,释放锁时需要调用writeLock().unlock()方法。

需要注意的是,在使用ReentrantReadWriteLock时,要保证读锁和写锁的获取和释放要在try…finally块中进行。

3.3 特性

ReentrantReadWriteLock有一些特性:

  • 重入性:同一个线程可以反复获取同一把锁,不会造成死锁。
  • 公平锁和非公平锁:ReentrantReadWriteLock提供了公平锁和非公平锁,与ReentrantLock类似。默认情况下,ReentrantReadWriteLock是非公平锁。
  • 支持读写分离:ReentrantReadWriteLock支持读写分离,提高并发读的效率,并且不会阻塞读和读之间的操作。
  • 降低锁的粒度:ReentrantReadWriteLock可以将锁的粒度降低,提高并发度。
  • 可重入:ReentrantReadWriteLock是可重入的。

3.4 总结

ReentrantReadWriteLock是一种可重入读写锁,适用于读多写少的场景,提供了公平锁和非公平锁、支持读写分离、降低锁的粒度等特性。使用起来比ReentrantLock稍微复杂一些,但也比较灵活。

4. StampedLock

4.1 基本概念

StampedLock(标记锁)是Java 8中新增的锁机制。它是一种乐观锁,允许多个线程在没有互相干扰的情况下访问共享资源。相比于ReentrantLock和ReentrantReadWriteLock,StampedLock更适用于读多写少、并发度高的场景,也可以用于提高读操作的性能。

4.2 使用方法

StampedLock的基本使用方法如下:

StampedLock lock = new StampedLock(); // 创建锁对象
long stamp = lock.tryOptimisticRead(); // 尝试获取乐观读锁
// 执行读操作
if (lock.validate(stamp)) {
    // 检查是否可用
    // 执行读操作
} else {
    // 尝试获取悲观读锁
    stamp = lock.readLock(); // 获取悲观读锁
    try {
        // 执行读操作
    } finally {
        lock.unlockRead(stamp); // 释放悲观读锁
    }
}

在上面的代码中,首先创建一个StampedLock对象,然后使用tryOptimisticRead()方法尝试获取乐观读锁。如果获取成功,则可以执行读操作;如果获取失败,则需要使用读锁(悲观锁)来获取资源。在获取锁后,执行完读操作后需要释放锁。

StampedLock还提供了其他类型的锁,如悲观读锁、写锁等。使用方法和ReentrantReadWriteLock类似。

你可能感兴趣的:(#,并发编程,后端)