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();
     }
   }
 }

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

相关文章
|
1天前
|
存储 Java 计算机视觉
Java二维数组的使用技巧与实例解析
本文详细介绍了Java中二维数组的使用方法
24 15
|
1天前
|
算法 搜索推荐 Java
【潜意识Java】深度解析黑马项目《苍穹外卖》与蓝桥杯算法的结合问题
本文探讨了如何将算法学习与实际项目相结合,以提升编程竞赛中的解题能力。通过《苍穹外卖》项目,介绍了订单配送路径规划(基于动态规划解决旅行商问题)和商品推荐系统(基于贪心算法)。这些实例不仅展示了算法在实际业务中的应用,还帮助读者更好地准备蓝桥杯等编程竞赛。结合具体代码实现和解析,文章详细说明了如何运用算法优化项目功能,提高解决问题的能力。
23 6
|
1天前
|
存储 算法 搜索推荐
【潜意识Java】期末考试可能考的高质量大题及答案解析
Java 期末考试大题整理:设计一个学生信息管理系统,涵盖面向对象编程、集合类、文件操作、异常处理和多线程等知识点。系统功能包括添加、查询、删除、显示所有学生信息、按成绩排序及文件存储。通过本题,考生可以巩固 Java 基础知识并掌握综合应用技能。代码解析详细,适合复习备考。
10 4
|
1天前
|
存储 Java
【潜意识Java】期末考试可能考的选择题(附带答案解析)
本文整理了 Java 期末考试中常见的选择题,涵盖数据类型、控制结构、面向对象编程、集合框架、异常处理、方法、流程控制和字符串等知识点。每道题目附有详细解析,帮助考生巩固基础,加深理解。通过这些练习,考生可以更好地准备考试,掌握 Java 的核心概念和语法。
|
1天前
|
Java 编译器 程序员
【潜意识Java】期末考试可能考的简答题及答案解析
为了帮助同学们更好地准备 Java 期末考试,本文列举了一些常见的简答题,并附上详细的答案解析。内容包括类与对象的区别、多态的实现、异常处理、接口与抽象类的区别以及垃圾回收机制。通过这些题目,同学们可以深入理解 Java 的核心概念,从而在考试中更加得心应手。每道题都配有代码示例和详细解释,帮助大家巩固知识点。希望这些内容能助力大家顺利通过考试!
|
3天前
|
自然语言处理 数据处理 索引
mindspeed-llm源码解析(一)preprocess_data
mindspeed-llm是昇腾模型套件代码仓,原来叫"modelLink"。这篇文章带大家阅读一下数据处理脚本preprocess_data.py(基于1.0.0分支),数据处理是模型训练的第一步,经常会用到。
12 0
Java——多线程高并发系列之ReadWriteLock读写锁
Java——多线程高并发系列之ReadWriteLock读写锁
636 3
Java——多线程高并发系列之ReadWriteLock读写锁
|
Java API
java多线程 -- ReadWriteLock 读写锁
写一条线程,读多条线程能够提升效率。 写写/读写 需要“互斥”;读读 不需要互斥. ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。
1047 0
|
15天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
71 17
|
26天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者

热门文章

最新文章

推荐镜像

更多