本文主要是介绍Java内置锁:深度解析ReentrantReadWriteLock并发类,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
ReentrantLock和ReentrantReadWriteLock是Java中用于线程同步的重要工具。ReentrantLock提供独占访问,适合需要保护共享资源不被并发修改的场景,同时支持可重入性,适用于递归操作。而ReentrantReadWriteLock则通过读写分离,允许多个线程同时读取资源,但仅允许一个线程写入,从而提高了并发性能。这种锁机制在处理大量读操作和较少写操作的场景中尤为有效。
定义
假如,有一个图书馆,图书馆每天都有读者前来借阅书籍,同时也有图书管理员在不断的更新和整理书架上的书籍。
在这个场景中,读者们就好比是线程中的读操作,他们只希望能够安静地阅读书籍,而不需要对书籍进行任何修改。图书管理员则好比是线程中的写操作,他们需要对书架上的书籍进行增加、删除或整理。为了保证图书馆的正常运营,需要制定一些规则来确保读者和管理员之间不会发生冲突,这就是ReadWriteLock接口发挥作用的地方,如下规则:
- 当有读者在阅读书籍时(即读锁被占用时),其他读者也可以同时阅读,因为读操作之间是不会相互干扰的,这就像多个读者可以同时站在不同的书架前阅读书籍一样。
- 当图书管理员需要整理书架时(即写锁被请求时),必须确保没有读者正在阅读那个书架上的书籍,否则整理过程中可能会导致读者找不到他们正在阅读的书籍,因此,写锁是独占的,一旦被图书管理员占用,其他读者和管理员都必须等待。
- 同时,当图书管理员正在整理书架时,其他管理员也不能同时进行整理工作,以免发生混乱。
通过ReadWriteLock接口,可以灵活地控制读操作和写操作之间的并发访问,从而提高程序的性能和响应能力。在这个图书馆的例子中,确保了读者能够高效地阅读书籍,同时管理员也能够安全地进行书架的整理工作。
代码案例
假设,有一个共享的数据结构,一个存储用户信息的Map
,这个Map
会被多个线程同时访问,有的线程需要读取用户信息(读操作),而有的线程需要更新用户信息(写操作),为了保证数据的一致性和并发性能,可以使用ReadWriteLock
来管理对Map
的访问,如下代码案例:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; public class UserStorage { // 存储用户信息的Map private final Map<String, String> userInfoMap = new HashMap<>(); // 读写锁 private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 获取用户信息(读操作) public String getUserInfo(String userId) { // 获取读锁 readWriteLock.readLock().lock(); try { // 从Map中读取用户信息 return userInfoMap.get(userId); } finally { // 释放读锁 readWriteLock.readLock().unlock(); } } // 更新用户信息(写操作) public void updateUserInfo(String userId, String userInfo) { // 获取写锁 readWriteLock.writeLock().lock(); try { // 更新Map中的用户信息 userInfoMap.put(userId, userInfo); } finally { // 释放写锁 readWriteLock.writeLock().unlock(); } }
} public class Client { public static void main(String[] args) { // 创建UserStorage实例 UserStorage userStorage = new UserStorage(); // 模拟多个线程同时访问UserStorage Runnable readTask = () -> { for (int i = 0; i < 5; i++) { String userId = "user" + i; String userInfo = userStorage.getUserInfo(userId); System.out.println("Read Thread: " + Thread.currentThread().getName() + ", User Info: " + userInfo); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }; Runnable writeTask = () -> { for (int i = 0; i < 5; i++) { String userId = "user" + i; String userInfo = "Info" + i; userStorage.updateUserInfo(userId, userInfo); System.out.println("Write Thread: " + Thread.currentThread().getName() + ", Updated User Info: " + userInfo); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 启动读线程和写线程 Thread readThread1 = new Thread(readTask, "ReadThread-1"); Thread readThread2 = new Thread(readTask, "ReadThread-2"); Thread writeThread = new Thread(writeTask, "WriteThread"); readThread1.start(); readThread2.start(); writeThread.start(); }
}
UserStorage类包含一个Map用于存储用户信息,以及一个ReadWriteLock用于控制对Map的并发访问,getUserInfo方法用于获取用户信息,它首先获取读锁,然后从Map中读取用户信息,最后释放读锁,updateUserInfo方法用于更新用户信息,它首先获取写锁,然后更新Map中的用户信息,最后释放写锁,Client类包含一个main方法,用于模拟多个线程同时访问UserStorage,这里创建了两个读线程和一个写线程,它们分别执行读任务和写任务。
运行结果如下,由于线程调度的不确定性,每次运行的结果可能会有所不同,但大致上,会看到读线程和写线程交替执行,读线程可以同时执行,而写线程在执行时会阻止其他线程获取写锁或读锁。
Write Thread: WriteThread, Updated User Info: Info0
Read Thread: ReadThread-1, User Info: Info0
Read Thread: ReadThread-2, User Info: Info0
Write Thread: WriteThread, Updated User Info: Info1
Read Thread: ReadThread-1, User Info: Info1
Read Thread: ReadThread-2, User Info: Info1
...
核心API
ReadWriteLock接口是java.util.concurrent.locks包中的一个重要接口,它定义了读取锁和写入锁的相关操作,ReadWriteLock允许对共享资源进行更高级的并发访问控制,通过分离读操作和写操作来提高并发性能,以下是ReadWriteLock接口中主要方法的作用:
- readLock(): 此方法返回一个用于读取操作的锁,多个读取锁可以同时被持有,而不会相互阻塞,但是在持有读取锁的情况下,任何尝试获取写入锁的线程都将被阻塞,直到所有读取锁被释放。
- writeLock(): 此方法返回一个用于写入操作的锁,写入锁是独占的,这意味着在给定时间内,只有一个线程能够持有写入锁,当一个线程持有写入锁时,其他任何尝试获取读取锁或写入锁的线程都将被阻塞。
核心总结
ReentrantLock和ReentrantReadWriteLock的区别?
1、ReentrantLock
把ReentrantLock想象成一把普通的锁,当一个线程拥有了这把锁,其他线程就不能再获得它,直到拥有锁的线程释放它,这种锁非常适合那些需要独占访问资源的场景,例如,当正在修改一个共享数据结构时,不希望其他线程同时修改它,这时你就可以使用ReentrantLock。此外,ReentrantLock还有一个很酷的特性,那就是可重入性,这意味着同一个线程可以多次获得同一个锁而不会发生死锁,这对于一些需要递归操作的场景非常有用。
2、ReentrantReadWriteLock
与ReentrantLock不同,ReentrantReadWriteLock把锁分为读锁和写锁两种,这是一个非常实用的设计,因为在很多应用场景中,读操作远多于写操作,而且多个读操作之间通常不会相互干扰。使用ReentrantReadWriteLock,多个线程可以同时获得读锁来读取资源,但只有一个线程可以获得写锁来修改资源,并且当有一个线程拥有写锁时,其他线程不能获得读锁或写锁。这种读写分离的设计可以大大提高并发性能,因为读操作不再受到写操作的阻塞。
3、总结
ReentrantLock和ReentrantReadWriteLock都是强大的线程同步工具,如果只需要独占访问资源,那么ReentrantLock是一个不错的选择,但如果应用中有大量的读操作和较少的写操作,并且希望提高并发性能,那么ReentrantReadWriteLock将是更好的选择。
这篇关于Java内置锁:深度解析ReentrantReadWriteLock并发类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!