@TOC
一、ReentrantLock 简介
ReentrantLock实现了Lock接口,并提供了和synchronized相同的互斥性和内存可见性以及可重入的加锁语义。和synchronized相比它再处理锁的不可用性上有更高的灵活性。
下面摘自JDK11文档:
ReentrantLock是一个可重入互斥锁,其基本行为和语义与使用同步方法和语句访问的隐式监视器锁相同,但具有扩展功能。
ReentrantLock由上次成功锁定但尚未解锁的线程所有。当该锁不属于另一个线程时,调用锁的线程将返回并成功获取该锁。如果当前线程已经拥有锁,则该方法将立即返回。这可以使用方法isHeldByCurrentThread和getHoldCount进行检查。
此类的构造函数接受一个可选的公平性参数。当设置为true时,在争用下,锁定有利于授予对等待时间最长的线程的访问权限。否则,此锁不能保证任何特定的访问顺序。使用许多线程访问的公平锁的程序可能显示出比使用默认设置的程序更低的总体吞吐量(即,更慢;通常慢得多),但变化较小。
二、ReentrantLock 和 synchronized 如何选择
2.1 ReentrantLock 优缺
ReentrantLock 和 synchronized 除了在内存和加锁语义上相同之外,ReentrantLock 还提供了定时的锁等待、可中断的锁等待、公平性、以及实现非块结构的加锁。在性能上ReentrantLock 稍优于synchronized 。但 ReentrantLock 的危险性更高,如果在 finally中忘记释放它,程序仍就能继续运行,但是这是一个大炸弹。
2.2 synchronized 优缺
synchronized 是大家都熟悉的一种锁,它简洁紧凑并且在线程转储中能给出在那些调用帧中获得了那些锁,也能检测和识别发生生死锁的线程。与之相比的 ReentrantLock 锁的非块结构决定了它无法确定那些调用帧中获得了那些锁🤣
2.3 结论
除非明确需要ReentrantLock 包含的伸缩性,否则还是 synchronized 更香。
三、读写锁
3.1 简要介绍
ReentrantLock 实现了标准的互斥锁,每次最多只有一个线程持有 ReentrantLock ,从数据的完整性来看互斥是一种强硬的加锁规则。因此很自然的并发就不要想了~
互斥避免了 写/写 冲突,写/读 冲突,但也造成了 读/读冲突😂,有得有失吧。但是吧,大部分情况下我们都在读读。
所以读写锁一般在一个资源被多个读或者多个写访问的时候使用,这两者不能同时存在。
3.2 ReadWriteLock
ReadWriteLock 暴露了两个 Lock 对象,一个是读,一个是写。这两个锁看起来是相互独立的,但是它们只是读-写锁对象的不同视图。
public interface ReadWriteLock {
/**
* 返回用于读取的锁。
*
* @return 用于阅读的锁
*/
Lock readLock();
/**
* 返回用于写的锁。
*
* @return 用于写的锁。
*/
Lock writeLock();
}
3.3 实现
ReadWriteLock 可以采用多种不同的实现方式,但同样的它们的性能、优先性、调度、公平性等方面也会不同。
读写锁是一种性能优化措施,在特定的情况下能实现更高的并发性。而在频繁读取的场景下,读写锁可以提高性能。但在其它情况下读写锁比独占锁要差一些,这是因为它们的复杂性更高。
读写锁有多种实现方式,其中都需要考虑以下几点:
- 释放有限
当一个写入操作释放写入锁时,并且队列中同时存在读线程和写线程,那么应该优先选择读线程,写线程,还是最先发出请求的线程? - 读线程插队
如果锁是由读线程持有,但有写线程正在等待,那么新到达的读线程能否立即获得访问权,还是应该在写线程后面等待?如果允许读线程插队到写线程之前,那么将提高并发性,但却可能造成写线程发生饥饿问题。 - 重入性
读取锁和写人锁是否是可重入的? - 降级
如果一个线程持有写人锁,那么它能否在不释放该锁的情况下获得读取锁? 这可能会使得写人锁被“降级”为读取锁,同时不允许其他写线程修改被保护的资源。 - 升级
读取锁能否优先于其他正在等待的读线程和写线程而升级为一个写入锁?在大多数的读一 写锁实现中并不支持升级,因为如果没有显式的升级操作,那么很容易造成死锁。(如果两个读线程试图同时升级为写人锁,那么二者都不会释放读取锁。)
说了这么多,我们举个例子看看:
/**
* Auth lhd
* Date 2023/8/3 10:00
* Annotate
*/
public class ReadwriteMape<K,V> {
private final Map<K,V> map;
private final ReadwriteLock lock = new ReentrantReadWriteLock() ;
private final Lock r = lock.readloek();
private final Lock w = lock.writeLock() ;
public ReadWriteMap(MapeK,V map){
this .map = map;
}
public V put(K key, V value) {
W.lock();
try {
return map.put (key, value) ;
}finally {
w.unlock();
}
}
// 对 romove(],putA11(),clear()等方法执行相同的操作
public V get (Object key){
r.lock();
try {
return map.get(key);
}finally {
r.unlock();
}
}
//对其他只读的 Map 方法执行相同的操作
}