AQS应用-ReentrantReadWriteLock(可重入读写锁)

本文深入解析可重入读写锁ReentrantReadWriteLock的工作原理,对比互斥锁,阐述其在高并发场景下的优势,包括读写操作的共享与独占特性,以及源码级的读锁和写锁分析。

生活

今天礼拜六,晴,加班,真好!

_____

今天来研究一下可重入读写锁-ReentrantReadWriteLock。
首先要明确的一点是,前面研究的可重入锁ReentrantLock,其实是一个互斥锁,保证在同一时刻只有一个线程获取资源,无论是写写,读读,读写任何场景都是互斥的。这种互斥锁在常见的高并发场景下会显现出糟糕的低吞吐量问题。
事实上,在高并发的实际场景下,读的频率远高于写,而且读本身不会对数据进行任何修改,只是读取,因此希望对读的操作进行共享,而对写操作进行独占。
这就是读写锁的作用。
它的特性就是:一个资源可以被多个读操作同时访问,或者被一个写操作独占。但是两者不能同时访问。

源码

初识ReentrantReadWriteLock

ReentrantReadWriteLock并没有继承自ReentrantLock 也 并不实现Lock接口,而是实现独有的ReadWriteLock接口。

此接口下仅有两个方法
 //获取读锁
  Lock readLock();

//获取写锁
  Lock writeLock();

如果创建一个读写锁,简单看下构造器

//默认创建非公平读写锁
    public ReentrantReadWriteLock() {
        this(false);
    }


//也可以通过指定fair 为true创建一个公平的读写锁
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
//至于这里的Sync 继承自AQS,FairSync和NonFairSync都继承自Sync,其实现与可重入锁中实现差不多。不赘述了、

从ReentrantLock的具体实现中得知,其独占性和重入性都是通过对AQS内state变量的操作实现的。
ReentrantReadWriteLock中由于需要实现读和写两个操作,把state这个int变量分为高16位和低16位。
高16位为读锁占用量,低16位为写锁占用量。

读锁分析

读锁lock()的方法下,关键方法如下


 protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();

          // 如果是独占锁(写锁)并且并不是当前线程独占,直接返回-1,获取读锁失败
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);


           // 三个判断
           //1、读锁是否需要被阻塞:非公平锁不需要阻塞、公平锁下,如果在等待队列中存在其他线程,则返回true,
           //否则返回false,往下走
           //2、获取读锁的数量小于最大数量
           //3、CAS操作将读锁占有量+1
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
//第一次获取读锁
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                    //可重入获取读锁,对应记录+1
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
 
                   //rh是记录最后一个
                    HoldCounter rh = cachedHoldCounter;
                    //如果并不是,就从ThreadLoccal取并设置为最后一个
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
//这里的代码同上基本差不多,不赘述
            return fullTryAcquireShared(current);
        }


当获取读锁失败,就进入AQS等待队列,在判断前方节点为SIGNAL状态后进入等待状态,等待被唤醒后竞争资源。
     

读锁unLock()方法

一目了然 没啥好说的。。
 protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    // Releasing the read lock has no effect on readers,
                    // but it may allow waiting writers to proceed if
                    // both read and write locks are now free.
                    return nextc == 0;
            }
        }
写锁分析

写锁lock()方法:

 protected final boolean tryAcquire(int acquires) {
        
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
             
             // 是独占锁且不说当前线程直接返回false
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            //是否需要阻塞,公平锁下,当等待队列中有其他正在等待的线程,需要阻塞
            
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

写锁unlock方法

很简单,不说了
protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }

案例

public class RRWLTest {
	
	public static void main(String[] args) {
		ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
		ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
		 ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
		CountDownLatch latch = new CountDownLatch(1);
		new Thread(new Test(readLock, null, 0, "A", latch)).start();
		new Thread(new Test(null, writeLock, 2000, "B", latch)).start();
		new Thread(new Test(readLock, null, 0, "C", latch)).start();
		new Thread(new Test(null, writeLock, -500, "D", latch)).start();
		new Thread(new Test(readLock, null, 0, "E", latch)).start();
		new Thread(new Test(readLock, null, 0, "F", latch)).start();
		new Thread(new Test(null, writeLock, 600, "G", latch)).start();
		try {
			Thread.sleep(1000L);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		latch.countDown();
		

		
	}
	
	static class Test implements Runnable{
		
		public static volatile int account =0;
		private ReentrantReadWriteLock.ReadLock readLock;
		private ReentrantReadWriteLock.WriteLock writeLock;
		private int money;
		private String name;
		private CountDownLatch latch;
		


		@Override
		public void run() {
			try {
				latch.await();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if(null!=readLock) {
				readLock.lock();
				System.out.println(String.format("name:%s,查询余额:%s", name,account));
				readLock.unlock();
			}
			else {
				writeLock.lock();
				account+=money;
				if(money>0) {
					System.out.println(String.format("name:%s,存款:%s,余额:%s", name,money,account));
	
				}else {
					System.out.println(String.format("name:%s,取款:%s,余额:%s", name,-money,account));

				}
				writeLock.unlock();
			}
			
		}



		public Test(ReadLock readLock, WriteLock writeLock, int money, String name, CountDownLatch latch) {
			super();
			this.readLock = readLock;
			this.writeLock = writeLock;
			this.money = money;
			this.name = name;
			this.latch = latch;
		}



		
		
		
		
	}

}

运行结果:
name:B,存款:2000,余额:2000
name:D,取款:500,余额:1500
name:G,存款:600,余额:2100
name:C,查询余额:2100
name:F,查询余额:2100
name:A,查询余额:2100
name:E,查询余额:2100

后记

明天礼拜天,可以睡懒觉了

<think>我们正在讨论Java中的ReentrantReadWriteLock读写锁。用户要求了解其使用方法和原理。根据之前的回答和引用,我们可以总结如下: 使用方面: - 读写锁适用于读多写少的场景,通过分离读锁(共享)和写锁(独占)提高并发性能。 - 基本使用:创建ReentrantReadWriteLock对象,然后获取读锁(readLock)和写锁(writeLock),在操作前后加锁和解锁。 - 锁降级:允许持有写锁的线程获取读锁,然后释放写锁,从而降级为读锁。但锁升级(持有读锁再获取写锁)是不允许的,因为可能导致死锁。 原理方面: - 锁的状态通过一个32位的整数(state)来维护,高16位表示读锁的持有数量,低16位表示写锁的重入次数。 - 读锁是共享锁,可以有多个线程同时持有;写锁是独占锁,同一时间只能有一个线程持有。 - 非公平模式下,写锁可以插队以减少写线程的等待时间;公平模式下则按照请求顺序分配锁。 根据引用[1][2][3]: - 引用[1]提到了读写锁实现缓存、读锁和写锁的获取与释放,以及锁降级(不能升级)。 - 引用[2]强调读写锁在读多写少场景下比排他锁(如synchronized)效率高。 - 引用[3]解释了不支持锁升级的原因:保证数据的可见性。如果多个线程持有读锁,其中一个线程升级为写锁并更新数据,这个更新对其他读线程是不可见的,因为其他读线程仍然持有读锁,无法感知到更新。 因此,我们的回答应该包括: 1. 读写锁的基本概念和适用场景。 2. 读写锁的使用方法(示例代码)。 3. 读写锁的核心原理(状态表示、锁获取和释放的过程)。 4. 锁降级的操作和原因,以及为什么不支持锁升级。 5. 注意事项(如避免锁升级、公平性选择等)。 此外,我们还要注意用户要求参考站内引用,所以回答中要自然地引用这些内容。 结构安排: - 一、读写锁概述(包括适用场景) - 二、基本使用方法(代码示例) - 三、核心原理(状态表示、读锁和写锁的获取与释放) - 四、锁降级与锁升级问题 - 五、注意事项 - 六、总结 最后,生成相关问题。 注意:在回答中,对于原理部分,我们可以用文字描述,必要时用公式表示状态分割(如state的高16位和低16位)。</think>### Java `ReentrantReadWriteLock` 使用与原理详解 `ReentrantReadWriteLock` 是 Java 并发包中的高性能读写锁,专为**读多写少**的场景设计。通过分离读锁(共享)和写锁(独占),显著提升并发性能[^2]。 --- #### 一、核心使用场景与基础用法 **适用场景**:缓存系统、配置中心、资源注册表等读操作占比超 80% 的场景[^2]。 ```java import java.util.concurrent.locks.ReentrantReadWriteLock; public class ThreadSafeCache { private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock(); private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock(); private Object data; // 读操作(共享锁) public Object getData() { readLock.lock(); // 多个线程可同时获取 try { return data; } finally { readLock.unlock(); } } // 写操作(独占锁) public void updateData(Object newData) { writeLock.lock(); // 仅一个线程可获取 try { data = newData; } finally { writeLock.unlock(); } } } ``` --- #### 二、核心实现原理 1. **状态表示** 通过 32 位 `state` 字段管理锁状态: - **高 16 位**:读锁持有数量(最大 65,535) - **低 16 位**:写锁重入次数(最大 65,535)[^1] ```java // 状态拆分示例 static final int SHARED_SHIFT = 16; static int sharedCount(int c) { return c >>> SHARED_SHIFT; } // 读锁数量 static int exclusiveCount(int c) { return c & 0xFFFF; } // 写锁重入次数 ``` 2. **锁获取规则** - **读锁**:当无写锁持有且无写锁等待时,多个线程可同时获取 - **写锁**:仅当无任何锁(读/写)持有时可获取 3. **公平性控制** ```java // 非公平模式(默认,吞吐量高) ReentrantReadWriteLock nonFairLock = new ReentrantReadWriteLock(); // 公平模式(按请求顺序分配锁) ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true); ``` --- #### 三、锁降级机制 **锁降级**:持有写锁的线程可获取读锁(避免其他写线程干扰),再释放写锁实现降级: ```java writeLock.lock(); try { // 1. 写操作 readLock.lock(); // 降级关键:写锁→读锁 } finally { writeLock.unlock(); // 释放写锁,保留读锁 } // 此时仍持有读锁 ``` **禁止锁升级**(读锁→写锁): ```java readLock.lock(); try { writeLock.lock(); // 死锁!当前线程阻塞等待自身释放读锁 } finally { ... } ``` **原因**:多个读锁共存时,若允许升级会导致数据不一致(其他读线程无法感知写操作)[^3]。 --- #### 四、底层同步机制 1. **读锁同步** - 使用 `Sync` 内部类的 `shared state` 管理 - 通过 `tryAcquireShared()` 实现 CAS 获取读锁 2. **写锁同步** - 基于 AQS(AbstractQueuedSynchronizer)的独占模式 - `tryAcquire()` 检查读锁/写锁状态 3. **锁重入实现** ```java // 写锁重入 writeLock.lock(); writeLock.lock(); // 允许同一线程重复获取 try { ... } finally { writeLock.unlock(); // 需两次解锁 writeLock.unlock(); } ``` --- #### 五、使用注意事项 1. **避免锁泄漏** 始终在 `finally` 块解锁: ```java readLock.lock(); try { /* 操作 */ } finally { readLock.unlock(); } // 确保解锁 ``` 2. **死锁预防** - 禁止锁升级 - 避免嵌套锁顺序不一致 3. **性能监控** ```java System.out.println(rwLock.toString()); // 输出示例: [Read locks = 3, WriteLocked by thread-1] ``` 4. **替代方案选择** 当写操作频繁时(>20%),优先使用 `ReentrantLock` 或 `synchronized`[^2]。 > **最佳实践**:读写锁在**读操作占比 ≥80%** 时性能优势显著,否则可能劣于独占锁[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值