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类似。