`
沉沦的快乐
  • 浏览: 56710 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

并发编程之读写锁ReentrantReadWriteLock实现

阅读更多

引子   

我们团队有维护这样一个类似比价的系统。其中有一个A方法需要查询好几个第三方的http接口,以获取某些信息进行汇总分析。虽然是通过多线程并发的去访问第三方接口,但是有些第三方系统不稳定,只要其中一个挂了就会影响整个方法的效率。团队成员经常在接到报警信息之后手动把不稳定的接口下线,这给大家的正常生活带来了麻烦,因为你有时候不得不不周末或者半夜起来操作。于是我做了一个自动下线功能,假如某个接口在1分钟内抛出的异常大于某个阈值之后自动下线一段时间,并在下线一段时间之后再自动上线,如果上线之后发现异常还没有减少则继续下线。为了实现这个功能,首先需要在抛异常的采集信息,并判断是否需要下线。A方法在请求第三方接口之前需要判断这个接口有没有下线,有下线之后则不再调用该http请求。在这种场景下,明显是读的行为比写的行为多,因为每次A方法都要读操作,而只有在抛异常的情况下才需要写操作。为了尽量保证方法的性能,想到了用读写锁来实现,但是之前没有用过读写锁,于是对读写锁的实现进行了学习。

读写锁

   读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。读写锁允许多个读者访问共享资源;读者和写者,多个写着只允许一个访问共享资源,因此在读操作多而写操作少的场景下,读写锁比普通的互斥锁能获得更高的并发能力。

 

读写锁规则

1.获得读锁:没有其他线程获得写锁,并且没有线程在请求写操作。

2.获得写锁:没有线程获得读锁和写锁。

3.可重入:如果一个线程获得读/写锁,那么该线程可以再次获得该锁。

4.读锁升级为写锁:有时候我们希望一个获得读锁的线程,也能获得写锁。读锁升级为写锁必须满足该线程是唯一拥有读锁的条件,即除了该线程之外,再没有其他线程拥有读锁。

5.写锁降级为读锁:即拥有写锁的线程,可以同时获得读锁。因为一个线程拥有了写锁,那么就不会有其他线程获得写锁和读锁了,而对于一个线程同时拥有读锁和写锁是没有什么危险的。

6.线程活跃度风险:如果读操作非常频繁,那么写操作可能一直获取不到写锁,从而产生写线程”饥饿“,那么需要一直机制去解决活跃度风险。

 

读写锁使用

java的读写锁是用ReentrantReadWriteLock实现的,ReentrantReadWriteLock使用比较简单,首先创建读写锁ReentrantReadWriteLock的实例,读方法用读锁锁住,写方法用写锁锁住,与所有显式锁一样,必须在finally中释放锁,下面是使用方法示例:

 

public ReadWriteLockExample {

       private final ReadWriteLock lock = new ReentrantReadWriteLock();
	private final Lock readLock = lock.readLock();
	private final Lock writeLock = lock.writeLock();


     public Object readMothed() {
          readLock.lock();
	     try {
		 your code;
	     } finally {
		readLock.unlock();
	    }
         }

    public void writeMothed() {
        writeLock.lock();
        try{
	    your code;
        } finally {
	    writeLock.unlock();
        }
    }
}
 

 

读写锁原理及实现

前面说过,java的读写锁是ReentrantReadWriteLock类实现的。他有3个自己实现的内部类ReadLock, writerLock,Sync。 同时Sync又有2个子类,一个是FairSync和NonFairSync,主要区别是获得读写锁的公平性,即解决线程饥饿的方法不同。

 

/** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics */
    private final Sync sync;
 

 

 

 顾名思义,ReadLock是读锁,writerLock是写锁,Sync是真正实现读写锁功能的类。ReadLock,writerLock的Lock,unLock方法都是委托Sync类来实现的。Sync扩展自抽象类AbstractQueuedSynchronizer,关于AQS的介绍,可以参考并发编程之AbstractQueuedSynchronizer原理剖析。使用一个int型state字段来管理读写请求线程数。高16位表示持有读锁的线程数,低16位表示持有写锁的线程数(0或1)以及请求写锁的请求数。使用threadlocal readHolds来记录当前线程持有的读锁数,同时还用了一个cachedHoldCounter来保存上一次成功获得读锁的线程及其读锁数量,还有一个exclusiveOwnerThread字段来保存拥有写锁的线程。最后Sync还继承出fairSync与nonfairSync类来解决线程活跃度问题。

读锁的实现

    读锁的lock是委托tryAcquireShared方法来实现的,如果tryAcquireShared返回小于0则通过自旋的方式继续调用tryAcquireShared方法直到获得锁或被中断为止。下面是tryAcquireShared的具体实现:

protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            if (sharedCount(c) == MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            if (!readerShouldBlock(current) &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != current.getId())
                    cachedHoldCounter = rh = readHolds.get();
                rh.count++;
                return 1;
            }
            return fullTryAcquireShared(current);
        }
 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
                return -1;  表示如果请求和持有写锁的线程数不为0并且持有写锁的线程不是当前线程 则不能获得读锁。言外之意就是,如果没有线程持有写锁也没有其他线程请求写锁时,有机会获得读锁,同时如果持有写锁的线程是当前线程,那么当前线程也有机会获得读锁。这实现了上面读写锁的第1,5条规则。

 由于state的低16位表示持有读锁的线程数,如果该数超过了0xFFFF抛出 Error("Maximum lock count exceeded");错误。:

if (sharedCount(c) == MAX_COUNT)
                throw new Error("Maximum lock count exceeded");

 如果前面两个条件都没有拦截住线程获取读锁的决心,那么最后一关来了:

 

 if (!readerShouldBlock(current) &&
                compareAndSetState(c, c + SHARED_UNIT))
 readerShouldBlock(current)方法判断当前线程需不需要继续阻塞,这个条件是为解决写锁活跃度风险。readerShouldBlock方法由Sync的子类fairSync和nonfairSync来实现。在nonfairSync中,如果阻塞队列中下一个请求是写请求,那么当前线程就不能获得读锁了。在fairSync中,如果阻塞队列为空或者当前线程是队列的head时,才允许获当前线程获得读锁。最后同时满足CAS条件时,这个线程才终于获得读锁了。

 

如果前面3个if条件都没能返回-1或1时,最终会调用fullTryAcquireShared方法再判断一遍完整的获取读锁逻辑,由于跟前面tryAcquireShared差别不大,只是增加了一个自旋直到能返回结果。

 

 final int fullTryAcquireShared(Thread current) {
            /*
             * This code is in part redundant with that in
             * tryAcquireShared but is simpler overall by not
             * complicating tryAcquireShared with interactions between
             * retries and lazily reading hold counts.
             */
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != current.getId())
                rh = readHolds.get();
            for (;;) {
                int c = getState();
                int w = exclusiveCount(c);
                if ((w != 0 && getExclusiveOwnerThread() != current) ||
                    ((rh.count | w) == 0 && readerShouldBlock(current)))
                    return -1;
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    cachedHoldCounter = rh; // cache for release
                    rh.count++;
                    return 1;
                }
            }
        }
 

 

 

写锁的实现

写锁的lock是委托Sync的tryAcquire方法来实现的,但是由于fairSync和nonfairSync对了公平性策略不同而采用可不同的实现方法,所以这里分开来写。

nonfairSync的写锁调用的是nonfairTryAcquire方法

 

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    首先,c==0表示当前既没有线程获得读锁,也没有线程获得写锁,那么只要当前线程对state的CAS操作成功,则就可以获得写锁,并设置当前线程为获得写锁的线程;如果有其他线程获得读锁或写锁时,如果获得写锁的线程是当前线程,那么可以继续获得写锁。这实现了前面读写锁规则的第2条和第3条。

 

fairSync的写锁实现

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (isFirst(current) &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

    该方法其实与nonfairSync的整体逻辑差不多,只是增加了一个是否阻塞的的判断isFirst(current)。当前既没有线程获得读锁,也没有线程获得写锁,那么当前线程有没有机会获得写锁,还得看阻塞队列的情况,如果阻塞队列为空,或者当前线程是阻塞队列的head,那么就有获得写锁的机会。

 

 

 

分享到:
评论

相关推荐

    8、读写锁ReentrantReadWriteLock&StampLock详解.pdf

    根据提供的文件信息,本文将详细解析读写锁`ReentrantReadWriteLock`以及`StampLock`在Java并发编程中的应用场景及其实现原理。 ### 一、读写锁介绍 #### 1.1 读写锁的基本概念 读写锁是一种特殊的锁机制,它可以...

    java并发编程艺术

    书中可能还会讨论读写锁(`ReentrantReadWriteLock`)以及条件变量,这些对于优化多线程程序的性能至关重要。 并发集合是Java并发编程中的重要组成部分,如`ConcurrentHashMap`, `CopyOnWriteArrayList`, `...

    关于读写锁算法的Java实现及思考

    关于读写锁算法的Java实现及思考,是一个深入探讨了多线程...通过本文的讲解,我们不仅学习到了读写锁的基本概念,也深入探讨了其在Java中的具体应用和自定义实现,这对于提高软件开发中的并发编程能力具有重要意义。

    java 读写锁代码

    Java 读写锁是Java并发编程中的一种重要机制,它为多线程环境下的数据访问提供了更为精细的控制。在Java的`java.util.concurrent.locks`包中,`ReentrantReadWriteLock`类实现了读写锁的功能。这个锁允许多个读取者...

    汪文君高并发编程实战视频资源下载.txt

    │ 高并发编程第一阶段30讲、如何实现一个自己的显式锁Lock精讲下(让锁具备超时功能).mp4 │ 高并发编程第一阶段31讲、如何给你的应用程序注入钩子程序,Linux下演示.mp4 │ 高并发编程第一阶段32讲、如何捕获...

    java并发编程实践pdf笔记

    - 除了互斥锁,还有读写锁(`ReentrantReadWriteLock`),允许多个线程同时读取但限制同时写入,提高了并发性能。 6. **其他并发工具** - `java.util.concurrent`包提供了一系列的并发工具类,如`Semaphore`...

    Java 读写锁实现原理浅析

    Java 读写锁实现原理浅析是 Java 并发编程中一个非常重要的主题。在多线程编程中,读写锁是解决读写并发问题的常用机制。本文主要介绍了 Java 读写锁实现原理浅析,包括读写锁的定义、读写锁的实现原理、...

    读写锁_读写锁_

    读写锁是多线程编程中的一个重要概念,用于提高并发访问数据时的效率。在并发环境中,如果多个线程同时读取数据,通常不会产生冲突,而写入数据时则可能引发问题。读写锁正是为了解决这个问题,它允许多个读取线程...

    Java并发编程之重入锁与读写锁

    Java并发编程之重入锁与读写锁 在 Java 并发编程中,重入锁和读写锁是两个非常重要的概念。重入锁是指支持重进入的锁,也就是说,一个线程可以多次获取同一个锁,而不会被阻塞。读写锁则是维护了一对相关的锁,一...

    Java并发编程全景图.pdf

    Java并发编程是Java语言中最为复杂且重要的部分之一,它涉及了多线程编程、内存模型、同步机制等多个领域。为了深入理解Java并发编程,有必要了解其核心技术点和相关实现原理,以下将详细介绍文件中提及的关键知识点...

    java并发编程实战.zip

    介绍了Java.util.concurrent包中的锁机制,如ReentrantLock、读写锁(ReentrantReadWriteLock)、Condition接口,以及Semaphore、CountDownLatch、CyclicBarrier等并发工具类的使用场景和实现原理。 5. **原子操作...

    Java并发编程进阶练习代码

    Java提供了多种锁机制,包括内置锁(synchronized)、显式锁(java.util.concurrent.locks包下的Lock接口及其实现)以及读写锁(ReentrantReadWriteLock)。理解并熟练使用这些锁能帮助你控制线程的执行顺序,解决竞...

    汪文君高并发编程实战视频资源全集

    │ 高并发编程第一阶段30讲、如何实现一个自己的显式锁Lock精讲下(让锁具备超时功能).mp4 │ 高并发编程第一阶段31讲、如何给你的应用程序注入钩子程序,Linux下演示.mp4 │ 高并发编程第一阶段32讲、如何捕获...

    并发编程(各种脑图资源集合).rar

    - 同步机制:互斥锁(synchronized)、读写锁(ReentrantReadWriteLock)、条件变量(Condition)等。 - 死锁预防和避免策略。 - 线程局部存储(ThreadLocal)。 - 异常处理和线程间通信。 6. **死锁——从产生...

    并发编程面试题(2020最新版)

    并发编程是当今软件开发中不可或缺的一部分,尤其是在多核处理器日益普及的背景下,合理地使用并发编程能够显著...读写锁ReentrantReadWriteLock允许多个读线程同时访问,而在写线程访问时,读线程和写线程都会被阻塞。

    JAVA并发编程实践 中文 高清 带书签 完整版 Doug Lea .pdf

    - **读写锁**:通过`ReentrantReadWriteLock`类实现,允许多个读操作并发进行,但写操作独占资源。 ### 三、高级并发技术 #### 3.1 线程池 - **Executor框架**:为创建和管理线程池提供了抽象层。 - **...

    JUC并发编程与源码分析视频课.zip

    9. **锁的高级特性**:包括读写锁(ReentrantReadWriteLock)、乐观锁(StampedLock)以及锁的可重入性、公平性和非公平性等概念。 10. **源码分析**:通过对JUC库中部分关键类的源码分析,帮助学员深入理解并发...

    Java 多线程与并发(12-26)-JUC锁- ReentrantReadWriteLock详解.pdf

    在Java多线程并发编程中,ReentrantReadWriteLock(可重入读写锁)是一个重要的同步工具,它属于Java并发包(java.util.concurrent.locks)中的一个类。这个锁提供了比标准的synchronized关键字更细粒度的控制,允许...

    Java并发编程学习笔记

    Java并发编程是Java开发中必不可少的一部分,涉及到多线程、同步机制、线程池以及并发工具类等多个核心知识点。以下是对这些主题的详细说明: 1. **线程安全与锁 Synchronized 底层实现原理**: 线程安全是指在多...

    java并发编程电子书

    并发编程是现代软件开发中的核心技能之一,尤其是在多核处理器普及后,利用并发来提高程序性能和响应速度变得至关重要。本书旨在帮助读者掌握Java平台上的并发工具、设计模式以及最佳实践。 1. **线程与进程** - *...

Global site tag (gtag.js) - Google Analytics