Java并发系列之三 ReadWriteLock源码解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Java并发系列之三 ReadWriteLock源码解析

1. ReadWriteLock接口



ReadWriteLock是一个java接口,它并没有继承Lock接口。提供了readLock()和writeLock(),分别返回一个读锁和写锁。

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}


2. ReentrantReadWriteLock使用



ReentrantReadWriteLock类内部定义了静态内部类ReadLock和WriteLock。同时持有 readLock和writerLock对象。


public class ReadWriterLockUsage {
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private Map<String, String> map = new HashMap<>();
    public String get(String key) {
        try {
            reentrantReadWriteLock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " readLock");
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return map.get(key);
        } finally {
            reentrantReadWriteLock.readLock().unlock();
            System.out.println(Thread.currentThread().getName() + " readUnlock");
        }
    }
    public void put(String key, String value) {
        try {
            reentrantReadWriteLock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + " writeLock");
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
            System.out.println(Thread.currentThread().getName() + " writeUnLock");
        }
    }
    public static void main(String[] args) {
        ReadWriterLockUsage usage = new ReadWriterLockUsage();
        for (int i = 0; i < 2; i++)
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    while (true)
                        usage.put("", "");
                }
            }.start();
        for (int i = 0; i < 2; i++)
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    while (true)
                        usage.get("");
                }
            }.start();
    }
}

运行结果可能如下


Thread-0 writeLock
Thread-0 writeUnLock
Thread-0 writeLock
Thread-0 writeUnLock
Thread-1 writeLock
Thread-1 writeUnLock
Thread-2 readLock
Thread-3 readLock
Thread-2 readUnlock
Thread-3 readUnlock
Thread-0 writeLock
Thread-0 writeUnLock
Thread-0 writeLock
Thread-0 writeUnLock
Thread-1 writeLock
Thread-1 writeUnLock
Thread-1 writeLock
Thread-1 writeUnLock

通过观察结果我们可以发现writeLock同一时刻只能被一个线程获得。而writeLock同一时刻可以被多个线程同时获得。


3. 源码解析



1. 锁状态


通过ReentrantLock的源码解析我们了解到,锁的状态由 private volatile int state来控制。每当线程获取到了锁 state会加1,每当线程释放了锁state会减1。当state=0表示锁当前处于非上锁状态。state定义在AbstractQueuedSynchronizer中。ReentrantLock持有一个AbstractQueuedSynchronizer对象。


那ReentrantReadWriteLock内部的锁是怎么实现的呢?我们知道ReentrantReadWriteLock内部有一个ReadLock和WriteLock。那么他们之间是完全独立的锁吗?如果不是独立的锁,那么state怎么来标识读写锁加锁的状态和次数呢?


事实上ReadLock和WriteLock是共用同一个AQS对象。AQS的state的值可以标识锁的状态。在读写锁的AQS实现中,int类型的state,我们知道int类型在计算机中有32位。它在读写所中,高16位标识读锁状态。低16位表示写锁状态。

    static final int SHARED_SHIFT   = 16;
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//值为FFFF
    //当前读锁(共享锁)被获取的数量 c >>> SHARED_SHIFT表示向右移动16个单位,结果是int的高16位
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    //写锁(独占锁)被获取的数量,低16位
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

2.WriteLock源码分析


首先我们来分析写锁的源码,因为写锁相对读锁更简单一些,写锁和ReentrantLock都是独占锁,所以我们先来分析它。还是从lock()开始分析


public static class  WriteLock implements Lock, java.io.Serializable {
    private final Sync sync;
    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    public void lock() {
        sync.acquire(1);
    }
}

我们知道sync的acquire(1)是个模板方法,先调用tryAcquire(1)方法如果获取成功,直接返回,如果获取失败,把线程封装成节点,加入队列,并且通过自旋来获取锁。在ReentrantLock源码分析中有详细讲解,读者如果不熟悉可以,先看ReentrantLock源码分析文章。我们重点来看下tryAcquire(1)在ReentrantReadWriteLock中的实现

 protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             *(如果读锁被获取 或者 写锁被获取而且获取到的线程不是当前线程,获取写锁失败)
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             *(如果锁的数量超过限制,获取失败)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             *(否则,如果该线程是可重入获取或队列策略允许,则该线程有资格进行锁定。 如果是这样,更新状态并设置所有者。)
             */
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                //如果 c != 0 and w == 0 表示读锁被线程占有了,获取锁失败
                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;
        }

如果当前读锁被获取了,再去获取写锁会失败。简单吧,如果失败了,走的还是ReentrantLock那一套


3. ReadLock源码解析


我们知道读锁是共享锁,同一时刻可以被多个线程同时获取。接下来我们来分析下。还是从lock()方法开始

 public static class ReadLock implements Lock, java.io.Serializable {
        private final Sync sync;
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
        /**
         * Acquires the read lock.
         *
         * <p>Acquires the read lock if the write lock is not held by
         * another thread and returns immediately.
         *
         * <p>If the write lock is held by another thread then
         * the current thread becomes disabled for thread scheduling
         * purposes and lies dormant until the read lock has been acquired.
         */
        public void lock() {
            //如果写锁没有被其他线程占有立马获取到读锁
            //如果写锁被其他线程占有,那么当前线程会阻塞直到读锁被获取到
            sync.acquireShared(1);
        }
}

我们来看下sync.acquireShared(1)方法

 public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)//如果尝试获取共享锁失败
            doAcquireShared(arg);

我们先来看下doAcquireShared()吧,如果是独占锁尝试获取失败,之后的流程我们是比较清楚的,我们可以对比观察他们之间的区别

 private void doAcquireShared(int arg) {
        //加入到AQS队列中 和独占锁是一样的
        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) {//如果获取成功
                    //获取成功共享锁和独占锁是有区别的,独占锁获取成功直接return。
                    //共享锁如果获取成功它会告诉下一个等待获取共享锁线程去获取共享锁
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

我们再来看下tryAcquireShared()的实现吧

protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                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;
            }
            return fullTryAcquireShared(current);
        }

关于锁降级



锁降级是指在获取写锁的同时,读锁也能获取到,当然这种情况只限于同一个线程。降级是对写锁而言,对于读锁个人觉得应该是锁升级。附上java源码的例子

class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        try {
          // Recheck state because another thread might have
          // acquired write lock and changed state before we did.
          if (!cacheValid) {
            data = ...
            cacheValid = true;
          }
          // Downgrade by acquiring read lock before releasing write lock
          rwl.readLock().lock();
        } finally {
          rwl.writeLock().unlock(); // Unlock write, still hold read
        }
     }
     try {
       use(data);
     } finally {
       rwl.readLock().unlock();
     }
   }
 }

下面那个读锁其实被升级为独占锁了,同一时刻只能被一个线程获取

相关文章
|
14天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
33 3
|
13天前
|
设计模式 算法 安全
实时操作系统(RTOS)深度解析及Java实现初探
【10月更文挑战第22天】实时操作系统(RTOS,Real-Time Operating System)是一种能够在严格的时间限制内响应外部事件并处理任务的操作系统。它以其高效、高速、可靠的特点,广泛应用于工业自动化、航空航天、医疗设备、交通控制等领域。本文将深入浅出地介绍RTOS的相关概念、底层原理、作用与功能,并探讨在Java中实现实时系统的方法。
45 1
|
6天前
|
安全 Java 测试技术
🎉Java零基础:全面解析枚举的强大功能
【10月更文挑战第19天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
96 60
|
2天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
8天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
34 10
|
4天前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
21 3
|
6天前
|
Java 程序员 开发者
Java中的异常处理机制深度解析####
本文将深入浅出地探讨Java编程语言中异常处理的核心概念与实践策略,旨在帮助开发者更好地理解如何构建健壮的应用程序。通过剖析异常体系结构、掌握有效的异常捕获与处理技巧,以及学习最佳实践,读者能够提升代码质量,减少运行时错误,从而增强软件的稳定性和用户体验。 ####
|
4天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
15 2
|
5天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
14 3
|
4天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
15 1

热门文章

最新文章

推荐镜像

更多