建站推广网站谷歌优化的最佳方案

news/2025/9/23 20:55:35/文章来源:
建站推广网站,谷歌优化的最佳方案,交互设计师,浙江建设继续教育网站首页目录 ReentrantReadWriteLock详解1、ReentrantReadWriteLock简介2、ReentrantReadWriteLock类继承结构和类属性3、ReentrantReadWriteLock的读写锁原理分析4、ReentrantReadWriteLock.WriteLock类的核心方法详解非公平写锁的获取非公平写锁的释放公平写锁的获取公平写锁的释放 … 目录 ReentrantReadWriteLock详解1、ReentrantReadWriteLock简介2、ReentrantReadWriteLock类继承结构和类属性3、ReentrantReadWriteLock的读写锁原理分析4、ReentrantReadWriteLock.WriteLock类的核心方法详解非公平写锁的获取非公平写锁的释放公平写锁的获取公平写锁的释放 5、ReentrantReadWriteLock.ReadLock类的核心方法详解非公平读锁的获取非公平读锁的释放公平读锁的获取非公平读锁的释放 6、读写锁的使用注意事项利用锁降级保证可见性和效率的做法举例谈一下 Single Threaded Execution模式谈一下Read-Write Lock模式 7、总结 ReentrantReadWriteLock详解 1、ReentrantReadWriteLock简介 ReentrantReadWriteLock 是 Java 并发包中的一个类这个类的字面意思是可重入的读写锁。 在关于ReentrantLock这篇文章中https://deepjava.blog.csdn.net/article/details/140112505 对ReentrantLock进行了详细说明ReentrantLock是独占锁某一时刻只有一个线程可以获取该锁这就导致在读多写少的场景下浪费了性能。因为多个线程对共享资源的读操作不加锁也不会出现线程安全问题。 ReentrantReadWriteLock 就是为了提升这种读多写少场景下的并发性能而设计的。 其采用读写分离的策略允许多个线程可以同时获取读锁也就是读锁是共享的写锁依然是互斥的。 2、ReentrantReadWriteLock类继承结构和类属性 类继承结构 public class ReentrantReadWriteLockimplements ReadWriteLock, java.io.Serializableabstract static class Sync extends AbstractQueuedSynchronizerstatic final class NonfairSync extends Syncstatic final class FairSync extends Syncpublic static class ReadLock implements Lock, java.io.Serializablepublic static class WriteLock implements Lock, java.io.Serializable描述 ReentrantReadWriteLock 实现了 ReadWriteLock 接口并且是 Serializable允许其对象进行序列化。这个类主要提供读写锁的实现并且管理读锁和写锁的状态。 Sync 是 ReentrantReadWriteLock 的内部抽象类继承自 AbstractQueuedSynchronizer (AQS)。Sync 负责管理读写锁的同步机制处理锁的获取和释放。它有两个主要的具体实现类NonfairSync 和 FairSync。 NonfairSync 是 Sync 的具体实现代表非公平锁的同步策略。它允许线程在获取锁时插队从而可能提升性能但可能导致线程饥饿。 FairSync 是 Sync 的具体实现代表公平锁的同步策略。它按照线程请求的顺序来获取锁避免线程饥饿但可能会带来额外的性能开销。 ReadLock 实现了 Lock 接口并且是 Serializable。它提供读锁的具体实现允许多个线程同时持有读锁只要没有线程持有写锁。ReadLock 提供了获取和释放读锁的方法。 类属性 // 读锁 ReadLock 是 ReentrantReadWriteLock 的静态内部类 private final ReentrantReadWriteLock.ReadLock readerLock;// 写锁 WriteLock 是 ReentrantReadWriteLock 的静态内部类 private final ReentrantReadWriteLock.WriteLock writerLock;// Sync 也是 ReentrantReadWriteLock 的静态内部类 final Sync sync;构造方法 ①、无参构造 public ReentrantReadWriteLock() {// 调用有参构造 默认非公平锁this(false);}②、有参构造 传true表示公平锁传false表示非公平锁 public ReentrantReadWriteLock(boolean fair) {sync fair ? new FairSync() : new NonfairSync();// 初始化读锁readerLock new ReadLock(this);// 初始化写锁writerLock new WriteLock(this);}3、ReentrantReadWriteLock的读写锁原理分析 这里先说结论方便下面对方法深入分析。(下面这段话部分截取自《Java并发编程之美》我觉得这本书对并发编程的解释比较通俗易懂比较推荐阅读。) 由上面ReentrantReadWriteLock类的继承结构可以看出读写锁的内部维护了一个 ReadLock 和一个 WriteLock它们依赖 Sync 实现具体功能。而 Sync 继承自 AQS并且也提供了公平和非公平的实现。我们知道 AQS 中只维护了一个 state 状态而 ReentrantReadWriteLock 则需要分别维护读状态和写状态。 一个 int 类型的state字段 怎么表示写和读两种状态呢 ? ReentrantReadWriteLock 巧妙地使用 state 的高 16 位表示读状态也就是获取到读锁的次数 使用低 16 位表示获取到写锁的线程的可重入次数。 看下Sync类的具体实现 static final int SHARED_SHIFT 16; // 读锁计数的位移量定义读锁计数在状态中的位置 static final int SHARED_UNIT (1 SHARED_SHIFT); // 读锁计数的单位等于 2^16 static final int MAX_COUNT (1 SHARED_SHIFT) - 1; // 读锁计数的最大值等于 2^16 - 1 static final int EXCLUSIVE_MASK (1 SHARED_SHIFT) - 1; // 用于提取写锁计数的掩码static int sharedCount(int c) {// 从状态中提取读锁的计数return c SHARED_SHIFT; // 右移 SHARED_SHIFT 位提取高 16 位 }static int exclusiveCount(int c) {// 从状态中提取写锁的计数return c EXCLUSIVE_MASK; // 使用 EXCLUSIVE_MASK 掩码提取低 16 位 }static final class HoldCounter {int count 0; // 当前线程获取写锁的次数 final long tid getThreadId(Thread.currentThread()); // 线程 ID避免垃圾回收 }static final class ThreadLocalHoldCounter extends ThreadLocalHoldCounter {Overridepublic HoldCounter initialValue() {// 初始化每个线程的 HoldCounter 实例return new HoldCounter();} }// 下面是对锁持有情况的高性管理设计 private transient ThreadLocalHoldCounter readHolds; // 每个线程的读锁持有计数器 private transient HoldCounter cachedHoldCounter; // 当前线程的写锁持有计数器缓存 private transient Thread firstReader null; // 记录第一个获取读锁的线程 private transient int firstReaderHoldCount; // 第一个获取读锁的线程持有的读锁次数// Sync 构造函数 Sync() {// 初始化 ThreadLocalHoldCounter用于每个线程独立的读锁持有计数readHolds new ThreadLocalHoldCounter();// 确保 readHolds 字段的可见性setState(getState()); }// Sync类的其他方法 省略总结 在 ReentrantReadWriteLock 中state 字段的高 16 位用于存储读锁计数低 16 位用于存储写锁计数。这种设计巧妙地利用了位运算来在一个整数中同时存储读锁和写锁的状态。具体来说 读锁计数通过将 state 右移16位来提取。写锁计数通过掩码操作提取。 此外为了支持写锁的可重入性HoldCounter 类用于记录线程对写锁的持有次数并通过 ThreadLocalHoldCounter 来保证每个线程有自己的 HoldCounter 实例。这样设计不仅提高了锁的性能还简化了锁的管理。 4、ReentrantReadWriteLock.WriteLock类的核心方法详解 非公平写锁的获取 // 创建非公平的 ReentrantReadWriteLock ReentrantReadWriteLock lock new ReentrantReadWriteLock(); // 获取写锁实例 ReentrantReadWriteLock.WriteLock writeLock lock.writeLock(); // 写锁上锁 writeLock.lock();WriteLock类的lock方法 public void lock() {// 实际调用的是 Sync内部类的 acquire方法// acquire方法 是Sync内部类 继承 AQS的 acquire方法sync.acquire(1); }AQS的 acquire方法 public final void acquire(int arg) {// 尝试获取锁调用 内部类Sync的 tryAcquire 方法// 如果 tryAcquire 返回 false说明无法直接获取锁// 需要将当前线程加入等待队列中if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 尝试将当前线程加入等待队列selfInterrupt(); // 如果线程被中断进行自我中断处理 } Sync内部类实现的tryAcquire方法 protected final boolean tryAcquire(int acquires) {Thread current Thread.currentThread(); // 获取当前线程int c getState(); // 获取当前锁的状态int w exclusiveCount(c); // 获取当前写锁的计数if (c ! 0) { // 如果当前锁的状态不为 0说明有锁持有// 如果当前写锁的计数为 0说明有读锁持有或者持有锁的线程不是当前线程if (w 0 || current ! getExclusiveOwnerThread())return false; // 当前线程无法获取写锁// 检查写锁计数是否超过最大允许值if (w exclusiveCount(acquires) MAX_COUNT)throw new Error(Maximum lock count exceeded); // 超过最大值抛出异常// 如果是重入锁更新锁的状态setState(c acquires); // 增加写锁计数return true; // 成功获取写锁}// 检查写锁是否应该阻塞例如是否存在其他写锁writerShouldBlock主要用于实现公平\非公平锁if (writerShouldBlock() ||!compareAndSetState(c, c acquires)) // 尝试更新锁的状态return false; // 更新失败返回 false// 更新当前线程为写锁的持有者setExclusiveOwnerThread(current); // 设置当前线程为写锁持有者return true; // 成功获取写锁 }NonfairSync类的writerShouldBlock方法 final boolean writerShouldBlock() {return false; // writers can always barge}总结 WriteLock.lock(): 调用 Sync.acquire(1) 尝试获取写锁。 Sync.acquire(int arg): 尝试通过 tryAcquire(arg) 方法直接获取写锁。 如果直接获取失败将当前线程加入等待队列并尝试从等待队列中获取锁。 Sync.tryAcquire(int acquires): 检查锁状态: 确保当前线程可以获取写锁如果锁被其他线程持有或计数超出最大值则返回 false。 尝试获取锁: 如果没有其他线程持有锁且状态更新成功将当前线程设置为写锁持有者并返回 true。 非公平写锁的释放 WriteLock类的unlock方法 // 释放写锁 public void unlock() {// 调用同步器AQS的release方法来释放锁sync.release(1); }AQS的release方法 // 释放锁尝试解除对当前线程的持有 public final boolean release(int arg) {// 调用tryRelease尝试释放锁并更新状态if (tryRelease(arg)) {// 获取当前的头结点Node h head;// 如果头结点不为空且其状态不为0唤醒头结点的后继节点if (h ! null h.waitStatus ! 0)unparkSuccessor(h); // 唤醒头结点的后继节点如果有return true;}return false; } Sync的tryRelease方法 protected final boolean tryRelease(int releases) {// 检查当前线程是否持有锁if (!isHeldExclusively())throw new IllegalMonitorStateException(); // 如果没有持有锁抛出异常// 计算释放后剩余的状态值int nextc getState() - releases;// 判断释放后是否没有线程持有锁即状态值为0// 从状态中提取写锁的计数 这里不用考虑读锁(高16位)因为获取写锁时读锁状态值肯定为0boolean free exclusiveCount(nextc) 0;// 如果锁已经完全释放将独占线程设置为nullif (free)setExclusiveOwnerThread(null);// 更新状态值setState(nextc);return free; // 返回锁是否完全释放的状态 } 总结 WriteLock.unlock(): sync.release(1) 调用会尝试释放一个写锁如果锁成功释放会通知等待的线程。 AQS.release(int arg): tryRelease(arg) 方法尝试释放锁并更新锁的状态。如果锁成功释放即 tryRelease 返回 true并且头结点的 waitStatus 不为0表示有线程在等待则调用 unparkSuccessor(h) 唤醒头结点的后继节点使其能够尝试获得锁。 Sync.tryRelease(int releases): 首先检查当前线程是否持有锁如果没有持有则抛出 IllegalMonitorStateException 异常。然后计算释放锁后的状态值如果状态值为0则表示锁完全释放这时候将独占线程设置为 null。最后更新状态值并返回锁是否完全释放的状态。 公平写锁的获取 公平写锁的获取过程和非公平写锁类似但通过 FairSync 类的 writerShouldBlock 方法实现公平性。 FairSync的writerShouldBlock 方法 final boolean writerShouldBlock() {// 检查队列中是否存在其他线程例如读线程或写线程在当前线程之前return hasQueuedPredecessors(); }AQS的hasQueuedPredecessors方法 public final boolean hasQueuedPredecessors() {// 检查当前线程前面是否有其他线程在等待// 正确性依赖于head在tail之前初始化并且head.next在当前线程是队列中的第一个线程时准确Node t tail; // 读取尾节点Node h head; // 读取头节点Node s;// 判断队列中是否有线程在当前线程之前// h ! t 确保队列中至少有两个节点// (s h.next) null 说明头节点的下一个节点为空即当前线程是第一个// s.thread ! Thread.currentThread() 确保队列中的下一个节点不是当前线程return h ! t ((s h.next) null || s.thread ! Thread.currentThread()); } 总结 公平性实现: FairSync.writerShouldBlock() 方法调用 hasQueuedPredecessors() 来检查队列中是否有其他线程在当前线程之前从而实现公平性。 hasQueuedPredecessors() 方法: 检查队列: 通过比较头节点和尾节点以及检查头节点的下一个节点来确定是否存在其他线程在当前线程之前确保公平性。 公平写锁的释放 同上面非公平写锁的释放步骤。 5、ReentrantReadWriteLock.ReadLock类的核心方法详解 非公平读锁的获取 因为读锁是共享锁所以调用的方法都是xxxShared命名的方法。 ReadLock的lock方法 public void lock() {// 调用同步器AQS的 acquireShared 方法来尝试获取一个共享读锁sync.acquireShared(1); }AQS的releaseShared方法 public final void acquireShared(int arg) {// 尝试获取共享锁通过 tryAcquireShared 方法// 如果 tryAcquireShared 返回负值说明无法直接获取锁// 需要进入等待队列if (tryAcquireShared(arg) 0)doAcquireShared(arg); // 进入等待队列并尝试获取锁 }Sync的tryReleaseShared方法 protected final int tryAcquireShared(int unused) {Thread current Thread.currentThread(); // 获取当前线程int c getState(); // 获取当前锁的状态// 如果有独占锁(写锁)持有并且持有锁的线程不是当前线程不能获取读锁if (exclusiveCount(c) ! 0 getExclusiveOwnerThread() ! current)return -1;int r sharedCount(c); // 获取当前读锁的计数// 如果读锁计数小于最大允许值且当前线程可以获取锁 (readerShouldBlock 用来保证是否公平)if (!readerShouldBlock() r MAX_COUNT compareAndSetState(c, c SHARED_UNIT)) {// 如果当前读锁计数为 0说明当前线程是第一个获取读锁的线程if (r 0) {firstReader current; // 设置当前线程为第一个读线程firstReaderHoldCount 1; // 设置持有读锁的计数} else if (firstReader current) {// 如果当前线程已经是第一个读线程增加持有读锁的计数firstReaderHoldCount;} else {// 更新读锁计数的缓存HoldCounter rh cachedHoldCounter;if (rh null || rh.tid ! getThreadId(current))cachedHoldCounter rh readHolds.get();else if (rh.count 0)readHolds.set(rh);rh.count;}return 1; // 成功获取读锁}// 如果直接获取失败调用 fullTryAcquireShared 方法进一步处理return fullTryAcquireShared(current); } NonfairSync的readerShouldBlock方法 // 使用启发式方法来避免写线程的饥饿决定读线程是否应该阻塞 final boolean readerShouldBlock() {// 调用AQS的apparentlyFirstQueuedIsExclusivereturn apparentlyFirstQueuedIsExclusive(); }// 检查队列中的头节点的下一个节点是否是一个非共享模式的节点即写线程。 // 如果是则说明队列中有写线程等待当前读操作应该阻塞 final boolean apparentlyFirstQueuedIsExclusive() {Node h, s;return (h head) ! null // 确保头节点存在(s h.next) ! null // 确保头节点的下一个节点存在!s.isShared() // 检查下一个节点是否不是共享模式即是写线程s.thread ! null; // 确保下一个节点的线程存在 } Sync的fullTryAcquireShared方法 final int fullTryAcquireShared(Thread current) {/** 这个方法在功能上与 tryAcquireShared 方法有一些重复* 但它整体上更简单因为它不涉及重试和延迟读取持有计数的交互。*/HoldCounter rh null; // 用于缓存持有计数的变量for (;;) {int c getState(); // 获取当前锁的状态// 检查是否有独占锁持有if (exclusiveCount(c) ! 0) {// 如果独占锁被持有且持有者不是当前线程无法获取读锁if (getExclusiveOwnerThread() ! current)return -1; // 返回 -1 表示无法获取锁// 否则当前线程持有独占锁在此阻塞将导致死锁。} else if (readerShouldBlock()) {// 检查是否需要阻塞读者线程确保不是递归获取读锁if (firstReader current) {// assert firstReaderHoldCount 0; // 当前线程是第一个读线程持有计数应大于 0} else {// 当前线程不是第一个读线程检查读锁持有计数if (rh null) {rh cachedHoldCounter; // 获取缓存的持有计数if (rh null || rh.tid ! getThreadId(current)) {// 如果缓存为空或不匹配当前线程 ID则获取持有计数rh readHolds.get();if (rh.count 0)readHolds.remove(); // 如果计数为 0则移除}}if (rh.count 0)return -1; // 如果读锁持有计数为 0则返回 -1 表示无法获取锁}}// 检查读锁计数是否超过最大允许值if (sharedCount(c) MAX_COUNT)throw new Error(Maximum lock count exceeded); // 超过最大值抛出异常// 尝试更新锁的状态if (compareAndSetState(c, c SHARED_UNIT)) {// 更新成功检查是否需要更新读线程的信息if (sharedCount(c) 0) {firstReader current; // 设置当前线程为第一个读线程firstReaderHoldCount 1; // 设置持有读锁的计数} else if (firstReader current) {// 如果当前线程已经是第一个读线程增加持有计数firstReaderHoldCount;} else {// 更新读锁计数的缓存if (rh null)rh cachedHoldCounter;if (rh null || rh.tid ! getThreadId(current))rh readHolds.get();else if (rh.count 0)readHolds.set(rh);rh.count;cachedHoldCounter rh; // 缓存用于释放读锁}return 1; // 成功获取读锁}} } AQS 的 doAcquireShared(int arg) 方法 private void doAcquireShared(int arg) {// 将当前线程封装为共享节点并加入等待队列final Node node addWaiter(Node.SHARED);boolean failed true; // 标记是否获取锁失败try {boolean interrupted false; // 标记是否中断for (;;) {final Node p node.predecessor(); // 获取当前节点的前驱节点// 如果前驱节点是头节点尝试获取共享锁if (p head) {int r tryAcquireShared(arg);if (r 0) {// 如果成功获取锁设置头节点并传播锁setHeadAndPropagate(node, r);p.next null; // 解除前驱节点的引用帮助垃圾回收if (interrupted)selfInterrupt(); // 处理中断failed false;return;}}// 如果获取锁失败判断是否需要挂起当前线程if (shouldParkAfterFailedAcquire(p, node) parkAndCheckInterrupt())interrupted true;}} finally {if (failed)cancelAcquire(node); // 如果获取锁失败取消加入等待队列} } 总结 ReadLock.lock() 方法: 调用 Sync.acquireShared(1) 尝试获取共享读锁。 Sync.acquireShared(int arg) 方法: 直接调用 tryAcquireShared(arg) 尝试获取读锁。 如果直接获取失败则调用 doAcquireShared(arg) 方法将线程加入等待队列。 Sync.tryAcquireShared(int unused) 方法: 检查锁状态: 如果独占锁存在且持有者不是当前线程返回 -1。 尝试获取锁: 如果可以直接获取锁则更新状态并设置读线程信息。 如果直接获取失败则调用 fullTryAcquireShared 进一步处理。 AQS.doAcquireShared(int arg) 方法: 将线程加入等待队列: 将当前线程封装为共享节点并加入等待队列。 尝试获取锁: 如果当前线程的前驱节点是头节点则尝试获取共享读锁。 如果获取锁失败则挂起当前线程等待锁的释放。 非公平读锁在获取锁时不会强制保证线程的公平性。线程可以在任何时候被允许获取读锁前提是没有其他线程持有独占锁(写锁)。 非公平读锁的释放 ReadLock的unlock() 方法 public void unlock() {sync.releaseShared(1); // 尝试释放一个共享读锁 }AQS的releaseShared(int arg) 方法 public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) { // 尝试释放共享锁doReleaseShared(); // 确保释放后正确地唤醒等待线程return true;}return false; }Sync的tryReleaseShared(int unused) 方法 protected final boolean tryReleaseShared(int unused) {Thread current Thread.currentThread(); // 获取当前线程// 检查当前线程是否为第一个读线程if (firstReader current) {// assert firstReaderHoldCount 0;if (firstReaderHoldCount 1)firstReader null; // 如果持有计数为 1清除第一个读线程elsefirstReaderHoldCount--; // 否则减少持有计数} else {// 当前线程不是第一个读线程更新持有计数HoldCounter rh cachedHoldCounter;if (rh null || rh.tid ! getThreadId(current))rh readHolds.get(); // 获取持有计数int count rh.count;if (count 1) {readHolds.remove(); // 如果计数为 1 或更少移除持有计数if (count 0)throw unmatchedUnlockException(); // 如果计数为 0 或更少抛出异常}--rh.count; // 减少持有计数}// 尝试更新锁的状态for (;;) {int c getState(); // 获取当前状态int nextc c - SHARED_UNIT; // 减少共享单位if (compareAndSetState(c, nextc)) { // 更新状态// 释放读锁对读者没有影响但可能允许等待的写线程继续return nextc 0; // 如果状态为 0返回 true 表示所有读锁已释放}} } AQS的doReleaseShared() 方法 private void doReleaseShared() {for (;;) {Node h head; // 获取头节点if (h ! null h ! tail) {int ws h.waitStatus;if (ws Node.SIGNAL) {if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))continue; // 循环以重新检查情况unparkSuccessor(h); // 唤醒后继节点} else if (ws 0 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue; // 循环以重新检查 CAS 失败的情况}if (h head) // 如果头节点改变继续循环break;} }总结 ReadLock.unlock() 方法: 调用 Sync.releaseShared(1) 尝试释放一个共享读锁。 AQS.releaseShared(int arg) 方法: 调用 tryReleaseShared(arg) 尝试释放共享读锁。 成功释放锁后调用 doReleaseShared() 确保正确地唤醒等待线程。 Sync.tryReleaseShared(int unused) 方法: 检查并更新读锁持有计数: 如果当前线程是第一个读线程更新相关信息。 如果当前线程不是第一个读线程更新缓存的持有计数。 更新锁状态: 尝试减少共享单位并更新状态。 返回 true 表示所有读锁已释放。 AQS.doReleaseShared() 方法: 确保释放后正确地唤醒等待线程。 如果需要传播信号将状态设置为 PROPAGATE。 公平读锁的获取 整体步骤和 非公平读锁的获取差不多。 公平性的保证主要通过readerShouldBlock方法保证。 FairSync类的readerShouldBlock方法 final boolean readerShouldBlock() {/** 确保公平性如果当前队列中存在其他线程且这些线程在队列中处于当前读线程之前* 则新来的读线程应当阻塞以保证公平性。*/return hasQueuedPredecessors(); }// AQS的hasQueuedPredecessors方法 public final boolean hasQueuedPredecessors() {Node t tail; // 获取队列的尾节点Node h head; // 获取队列的头节点Node s; // 下一个节点/** 确保头节点存在并且不是队尾节点且头节点的下一个节点不是当前线程。* 如果条件成立说明队列中有其他线程在等待当前线程需要阻塞。*/return h ! t ((s h.next) null || s.thread ! Thread.currentThread()); } 总结 FairSync.readerShouldBlock(): 通过调用 hasQueuedPredecessors() 方法来决定当前读线程是否应该阻塞。主要用于维护公平性确保新来的读线程在获取锁之前如果队列中有其他等待的线程则阻塞。 hasQueuedPredecessors(): 检查队列中是否存在其他线程在当前线程之前等待。如果队列中有等待的线程特别是写线程则返回 true表示当前线程应该阻塞。 这些方法通过确保读线程在获取锁之前检查是否有其他等待的线程从而保证了读锁的公平性。 非公平读锁的释放 同非公平读锁的释放步骤。 6、读写锁的使用注意事项 利用锁降级保证可见性和效率的做法 补充知识点 这里说的锁降级是指线程在持有写锁的前提下获取读锁再释放写锁的过程。 注意全程都是有锁的状态。 但是不能进行锁升级也就是持有读锁的前提下获取写锁因为写锁是互斥的。 举例 我养了几只特别厉害的狗这几只狗会做大骨汤等骨头准备好了所有的狗狗就可以同时并发的吃骨头。如果骨头没准备好狗狗想吃骨头就得等做骨头的那只狗先把骨头汤煮好才能全部开吃。 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock;public class TestA {private static final ReentrantReadWriteLock lock new ReentrantReadWriteLock();private static final Lock readLock lock.readLock();private static final Lock writeLock lock.writeLock();private static volatile boolean bonePrepared false; // 是否准备好骨头public static void main(String[] args) throws Exception {Thread t1 new Thread(() - {try {useBone();} catch (Exception e) {e.printStackTrace();}}, 秀逗);Thread t2 new Thread(() - {try {useBone();} catch (Exception e) {e.printStackTrace();}}, 四眼);Thread t3 new Thread(() - {try {useBone();} catch (Exception e) {e.printStackTrace();}}, 大黄);t1.start();t2.start();t3.start();}// 真正给狗狗调用的方法public static void useBone() throws Exception {readLock.lock();try {if (!bonePrepared) {// 如果骨头未准备好先释放读锁readLock.unlock();writeLock.lock();try {if (!bonePrepared) {// 准备骨头prepareBone();bonePrepared true;}// 准备好后重新获取读锁readLock.lock();} finally {// 释放写锁writeLock.unlock();}// 锁降级完成读锁已重新获取}// 使用骨头的过程eatBone();} finally {// 释放读锁readLock.unlock();}}private static void prepareBone() throws Exception {// 骨头准备的具体过程System.out.println(机器开始自动准备骨头...);System.out.println(切骨头...);System.out.println(煮骨头...);System.out.println(放调味料...);System.out.println(准备好啦...);try {Thread.sleep(2000); // 模拟准备过程} catch (InterruptedException e) {throw e;}}private static void eatBone() throws Exception {// 吃骨头的过程System.out.println(Thread.currentThread().getName() 开始吃骨头...);try {Thread.sleep(500); // 模拟吃骨头的过程} catch (InterruptedException e) {throw e;}}} 总结 假设秀逗跑的最快。 线程获取读锁 秀逗首先尝试获取读锁因为读取操作通常是安全的多个线程可以并发读取数据。 由于初始时骨头还未准备好秀逗发现需要准备骨头于是释放读锁并获取写锁。 获取写锁并准备骨头 秀逗获取写锁后开始准备骨头如制作骨头汤。写锁是独占的这确保了在准备骨头的过程中没有其他线程能够修改或读取骨头。 由于写锁是互斥的其他线程必须等待秀逗完成骨头准备。 锁降级 准备完骨头后秀逗需要释放写锁以允许其他线程访问骨头。 在释放写锁之前秀逗再一次获取读锁。这样秀逗在告知其他狗子骨头准备好之前自己相当于先盛了一碗骨头汤。 (还是秀逗聪明~ 自己做的自己先盛一碗没毛病吧~ ) 通知其他线程 一旦秀逗获取到读锁就释放写锁(虽然秀逗先偷偷盛了一碗但仍然等通知了其他狗子之后再吃秀逗还是很讲义气的~)其他狗子得到通知也能获取读锁并开始吃骨头。 通过这种方式秀逗保证了自己首先获取读锁同时公平地让其他线程也能得到通知获取读锁。 读写锁一般还可以用来实现线程安全的缓存。这里就不写示例了。 谈一下 Single Threaded Execution模式 下面摘自《图解Java多线程设计模式》 有一座独木桥非常细每次只允许一个人经过。如果这个人还没有走到桥的另一头则下一个人无法过桥。如果同时有两个人上桥桥就会塌掉掉进河里。 所谓 Single Threaded Execution 模式意即“以一个线程执行”。就像独木桥同一时间内只允许一个人通行一样该模式用于设置限制以确保同一时间内只能让一个线程执行处理。 Single Threaded Execution有时候也称为临界区(critical section)或临界域(critical region )Single Threaded Execution这个名称侧重于执行处理的线程(过桥的人)而临界区或临界域的名称则侧重于执行范围(人过的桥)。 我觉得这个模式算是多线程同步的基础。也可以算是互斥锁的基础思想。 上面例子中 秀逗准备骨头汤的过程就是 Single Threaded Execution。 而整个例子又是Read-Write Lock模式。 谈一下Read-Write Lock模式 学生们正在一起看老师在黑板上写的板书。这时老师想擦掉板书再写新的内容。而学生们说道:“老师我们还没看完请先不要擦掉!”于是老师就会等待大家都看完。 我觉得这个解释的角度很有意思从读锁的角度解释。 一般我们理解读写锁容易从写锁角度去理解比如写的过程中不能读不能写。 上面的解释也很到位因为写锁是互斥的也要等没有读锁的时候才能获取。 在 Read-Write Lock模式中读取操作和写入操作是分开考虑的。在执行读取操作之前线程必须获取用于读取的锁。而在执行写入操作之前线程必须获取用于写人的锁。 由于当线程执行读取操作时实例的状态不会发生变化所以多个线程可以同时读取。但在读取时不可以写入。 当线程执行写人操作时实例的状态就会发生变化。因此当有一个线程正在写入时其他线程不可以读取或写入。 一般来说执行互斥处理会降低程序性能。但如果把针对写入的互斥处理和针对读取的互斥处理分开来考虑则可以提高程序性能。 7、总结 ReentrantReadWriteLock 只是读写锁思想的一个具体Java实现。 重要的是理解这种思想。掌握这些思想可以帮助我们在不同编程语言或框架中应用类似的锁机制。 参考资源(非常感谢下面这些资料) 《图解Java多线程设计模式》 《Java并发编程的艺术》 https://pdai.tech/md/java/thread/java-thread-x-lock-ReentrantReadWriteLock.html https://javaguide.cn/java/concurrent/java-concurrent-questions-02.html#reentrantreadwritelock

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/913863.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网站建设桂林长沙做网站比较好的公司

Couchdb 命令执行漏洞复现 (CVE-2017-12636) 1、下载couchdb.py 2、修改目标和反弹地址 3、Python3调用执行即可 couchdb.py文件下载地址: https://github.com/vulhub/vulhub/blob/master/couchdb/CVE-2017-12636/exp.py ‍ 在VULFocus上开启环境 …

做推广送网站免费建站做一网站要学些什么

Xshell 是一个强大的终端仿真器,它支持多种Linux发行版的远程连接。Xshell提供了一系列的快捷键,以提高用户的操作效率。以下是一些Xshell中常用的快捷键: 新建会话窗口: Ctrl N 或 Ctrl Shift N 在现有会话中打开新标签&…

网站开发合作协议书论述站点的几种推广方式

哈工大人工智能暑期课实践项目建议 这个博客介绍了暑期课实践作业的建议。 时间:7/10 - 7/22. 一周上课, 一周项目实践。 要求:项目实践的过程请用公开的博客记录。 项目的源代码请放到 github 中。 每4 ~ 5 人一个小组,从下…

google 网站收录优秀网站建设模板

1.生成key edit->preferences->license Keys->generate 2.新建product license文件 3.新建Zend Guard项目文件 需要注意新建项目的第二项需要英文路径 4.在项目上按右键 选择configure 初始界面是 如果要用做授权,点击security按键 然后设置license文件等 附上php.in…

以下哪些不属于h5制作软件成都网站排名优化

文章目录1. 题目2. 解题1. 题目 一个班级里有 n 个学生,编号为 0 到 n - 1 。 每个学生会依次回答问题,编号为 0 的学生先回答,然后是编号为 1 的学生,以此类推,直到编号为 n - 1 的学生,然后老师会重复这…

黑链 对网站的影响济南做网站软件

🔍目的 为另一个对象提供代理或占位符以控制对其的访问。 🔍解释 真实世界例子 想象有一个塔,当地的巫师去那里学习他们的法术。象牙塔只能够通过代理来进入以此来保证只有首先3个巫师才能进入。这里的代理就代表的塔的功能并添加访问控制。 …

青海省住房和城乡建设局网站首页谁会在西安做网站的吗

Google Analytics(谷歌分析)是最受欢迎的网站分析工具之一。它为网站管理员提供了深入了解其网站访问者的机会,并通过数据分析提供有关网站流量、用户行为和转化率的洞察。 1、跟踪代码(Tracking Code) 跟踪代码是嵌入…

2025年9月23日 - 20243867孙堃2405

今天就两门课,工程实训劳动课,学会了如何安全用电,以及组装电路,还有一节英语课,丰富了词汇与语法,新背会了20个单词,晚上没事去打了打篮球,总之这一天很充实

网站建设与管理视频教程wordpress跳转后端IP

关于昨天 Solv 携手 zCloak 与新加坡和加纳两个央行合作的 Project DESFT,很多朋友都发来恭喜和祝福,并希望了解详情。这个事我们秘密努力了半年多,终于有一个阶段性的成果。这里我转载中文版官宣新闻稿,欢迎大家关注。等我忙过这…

电子商务网站设计实践报告速成网站

CentOS6中关于网络配置的命令有很多,本文将介绍几个平时最长用的几个命令,以及网卡IP地址的配置和简单路由配置。1、经常使用的查看IP地址命令为 ifconfig,不跟参数的情况下默认查看所有已启用的网卡信息,如下图所示:如…

余姚网站seo运营广州智迅网络做网站

在 Java 中:为什么不能在 static 环境中访问非 static 变量? 1、静态(static)变量2、非静态(非static)变量3、为什么不能访问?4、如何访问?5、总结 💖The Begin&#x1f…

做网站的哪里有wordpress菜单导航代码

MyBatis 操作数据库 本节目标前⾔JDBC 操作⽰例回顾1. 什么是MyBatis?2. MyBatis⼊⻔2.1 准备⼯作2.1.1 创建⼯程2.1.2 数据准备 2.2 配置数据库连接字符串2.3 写持久层代码2.4 单元测试 3. MyBatis的基础操作3.1 打印⽇志3.2 参数传递3.3 增(Insert)3.4 删(Delete)3.5 改(Upd…

企业网站优化公司wordpress切换回老的编辑器

问题来源:在表单的标签中对输入的字符串进行大写转换。一不小心输入了反斜杠 \如下图所示:输入 chn\ 的时候,在 IE8 下弹出一个js错误。(在实际的项目的表单元素中遇到了,单独这样拿出来测试的时候又不弹出错误。也很是焦灼)索…

网站建设的页面要求wordpress谷歌地图定位修改设置

#、%和$符号在OGNL表达式中经常出现,而这三种符号也是开发者不容易掌握和理解的部分。在这里笔者简单介绍它们的相应用途。1.#符号的用途一般有三种。 1)访问非根对象属性,例如示例中的#session.msg表达式,由于Struts 2中值栈被…

wordpress rateseo 新旧网站 两个域名

0、序 在运维这条路上走久了,你能听到或者遇到这样的事情就越多,甚至是你自己干过的: 一个信心满满的运维人员一个不小心,输入 "chmod -R 777 /" 导致一个巨大的悲剧,然后整个部门从上到下被撸一顿。虽然…

做网站需要编码吗原型图怎么做网站交互

STM32——DMA 宗旨:技术的学习是有限的,分享的精神是无限的。 DMA 是为CPU分担数据转移的工作。因为DMA的存在CPU才被解放出来,它可以在 DMA 转移数据的过程中同时进行数据运算、响应中断,大大提高效率。 1、DMA工作分析 数据传…

2025.9.23——1绿

普及+/提高 P2602 [ZJOI2010] 数字计数 很早之前就看到但不想写的数位DP,昨天开始写,用的递归,一直错,看完题解才发现不需要用递归也能解决。 然后今天对着题解思路+AI解析+代码才堪堪完成,我对数位DP还是太不了解…

网站注册界面设计查看商标是否被注册官网

目录 一、requests的基本使用 二、get请求 三、post请求 四、代理的使用 五、cookie登录以及验证码图片识别 一、requests的基本使用 import requestsurl http://www.baidu.comresponse requests.get(url url)#一个类型和六个属性 #Response类型 #print(type(response…

指定图片做logo网站新手怎样推销自己的产品

从CSDN中读取到关于spark structured streaming源代码分析不错的几篇文章 spark源码分析--事件总线LiveListenerBus spark事件总线的核心是LiveListenerBus,其内部维护了多个AsyncEventQueue队列用于存储和分发SparkListenerEvent事件。 spark事件总线整体思想是生产…