JAVA并发之ReentrantReadWriteLock原理解析(二)

629 阅读3分钟

本篇继上一篇《JAVA并发之ReentrantReadWriteLock原理解析(一)》,接着介绍Java并发包内的ReentrantReadWriteLock工具类的写锁。

写锁原理

写锁相对于读锁而言,逻辑相对简单一些,下面我们先看下写锁的主要逻辑和调用链情况。

其中主要的方法有两个:

  • tryAcquire
  1. 如果有线程(可以是自己)拿有读锁或者其他线程拿有写锁,当前线程就调用acquireQueued进AQS队列等待;
  2. 如果拿有写锁的线程是当前线程,当前线程获取写锁结束(可重入);
  3. 如果读写锁当前没有任何线程获取锁,那要分公平锁和非公平锁而定:
  4. 公平锁:如果AQS当中有线程在排队,则当前线程调用acquireQueued进入AQS排队
  5. 非公平锁:当前线程尝试获取写锁,成功则返回,否则调用acquireQueued进入AQS排队
  • acquireQueued
  1. 再次尝试一下tryAcquire(万一其他线程释放锁了呢)
  2. 如果成功拿到锁,则返回去
  3. 如果不成功,则标记AQS中前节点,告诉它等你释放锁的时间记得唤醒我,然后休眠等待被唤醒

写锁验证

下面通过例子来看下读锁的几种情况:

情况1: 当前没有线程拿去读/写锁

结论:几个线程竞争获取锁,获取失败的进入AQS排队

/**
     * 同时获取写锁,则排队依次执行
     */
    public static void testWriteLockConcurrently() {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        for (int i = 0; i < 3; i++) {
            DemoThread thread = new DemoThread("WriteThread" + i, readWriteLock.writeLock());
            thread.start();
        }
    }
    
    static class DemoThread extends Thread {
        private DateFormat df = new SimpleDateFormat("HH:mm:ss---");
        private Lock lock;
        
        public DemoThread(String name, Lock lock) {
            super(name);
            this.lock = lock;
        }
        
        @Override
        public void run() {
            try {
                lock.lock();
                System.out.println(df.format(new Date()) + getName() + " is working.");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(df.format(new Date()) + getName() + " finished work.");
                lock.unlock();
            }
            
        }
}

当有三个线程同时获取写锁时,三个线程只能依次获取,控制台打印日志如下:

18:02:28---WriteThread0 is working.
18:02:33---WriteThread0 finished work.
18:02:33---WriteThread1 is working.
18:02:38---WriteThread1 finished work.
18:02:38---WriteThread2 is working.
18:02:43---WriteThread2 finished work.

情况2: 当前线程先拿到读锁

结论:当前线程排队等待读锁释放后获取

/**
     * 当前线程先获取读锁,不能再获取写锁,需要等读锁释放才行
     */
    public static void testGetWriteLockAfterUnlockReadLockForSameThread() throws Exception {
        DateFormat df = new SimpleDateFormat("HH:mm:ss---");
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
        readLock.lock();
        System.out.println(df.format(new Date()) + "MainThread got read lock.");
        Thread.sleep(500);
        System.out.println(df.format(new Date()) + "MainThread try to get write lock.");
        writeLock.tryLock(3, TimeUnit.SECONDS);
        System.out.println(
                df.format(new Date()) + "Current write lock hold count is:" + readWriteLock.getWriteHoldCount());
        readLock.unlock();
}

主线程先获取了读锁,然后接着尝试获取写锁(超时3秒钟),一直未获得,getWriteHoldCount返回的是0

18:11:56---MainThread got read lock.
18:11:57---MainThread try to get write lock.
18:12:00---Current write lock hold count is:0

情况3: 其他线程拿到读锁或者写锁

结论:当前线程排队等待读锁释放后获取

情况2时,当前线程都不能立刻拿到解锁,情况3更是不可能了。

参考文章

JAVA并发之ReentrantLock原理解析

JAVA并发之ReentrantReadWriteLock原理解析(一)

Demo代码位置


src/main/java/net/weichitech/juc/ReentrantReadWriteLockTest2.java · 小西学编程/java-learning - Gitee.com