读写锁的详细实践

367 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 28 天,点击查看活动详情

大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈


前言

JUC并发包中提供了ReentrantReadWriteLock作为读写锁,本篇文章将对读写锁的如下四个场景进行演示。

  1. 当前线程获取读锁时,读锁是否被获取不会影响读锁的获取
  2. 当前线程获取读锁时,若写锁未被获取或者写锁被当前线程获取,则允许获取读锁,否则进入等待状态
  3. 当前线程获取写锁时,若读锁已经被获取,无论获取读锁的线程是否是当前线程,都进入等待状态
  4. 当前线程获取写锁时,若写锁已经被其它线程获取,则进入等待状态

正文

一. 场景一

本小节演示:当前线程获取读锁时,读锁是否被获取不会影响读锁的获取。示例如下。

public class ReentrantReadWriteLockTest {

    @Test
    public void 当前线程获取读锁_读锁是否被获取不会影响读锁的获取() throws Exception {
        // 创建读写锁
        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        Lock readLock = reentrantReadWriteLock.readLock();

        // 创建CountDownLatch对象
        CountDownLatch countDownLatch = new CountDownLatch(2);

        // 创建任务
        Runnable readLockRunnable1 = new Runnable() {
            @Override
            public void run() {
                readLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " 获取读锁");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // ignore
                } finally {
                    readLock.unlock();
                    countDownLatch.countDown();
                }
            }
        };
        Runnable readLockRunnable2 = new Runnable() {
            @Override
            public void run() {
                readLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " 获取读锁");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // ignore
                } finally {
                    readLock.unlock();
                    countDownLatch.countDown();
                }
            }
        };

        // 线程1先获取读锁
        Thread thread1 = new Thread(readLockRunnable1, "线程1");
        thread1.start();

        // 线程2再获取读锁
        Thread thread2 = new Thread(readLockRunnable2, "线程2");
        thread2.start();

        // 阻塞主线程
        countDownLatch.await();
    }

}

上述示例中,演示了线程1和线程2先后获取读锁,如果获取到读锁,则打印一条日志。运行测试程序,结果如下。

场景一

运行结果表明,当前线程获取读锁时,读锁是否被获取不会影响读锁的获取。

二. 场景二

本小节演示:当前线程获取读锁时,若写锁未被获取或者写锁被当前线程获取,则允许获取读锁,否则进入等待状态。示例如下。

public class ReentrantReadWriteLockTest {

    @Test
    public void 当前线程获取读锁_若写锁已经被当前线程获取则当前线程能获取到读锁() throws Exception {
        // 创建读写锁
        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        Lock readLock = reentrantReadWriteLock.readLock();
        Lock writeLock = reentrantReadWriteLock.writeLock();

        // 创建CountDownLatch对象
        CountDownLatch countDownLatch = new CountDownLatch(1);

        // 创建任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 先拿写锁
                writeLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " 获取到了写锁");
                    // 同一线程中再拿读锁
                    if (readLock.tryLock()) {
                        try {
                            System.out.println(Thread.currentThread().getName() + " 获取到了读锁");
                        } finally {
                            readLock.unlock();
                        }
                    }
                } finally {
                    writeLock.unlock();
                    countDownLatch.countDown();
                }
            }
        };

        // 创建线程
        Thread thread = new Thread(runnable, "线程1");
        thread.start();

        // 阻塞主线程
        countDownLatch.await();
    }

}

上述示例中,演示了同一线程先后获取写锁和读锁,获取到对应锁后,打印日志。运行测试程序,结果如下所示。

场景二_1

运行结果表明,当前线程获取读锁时,若写锁被当前线程获取,则允许获取读锁

再看如下示例。

public class ReentrantReadWriteLockTest {

    @Test
    public void 当前线程获取读锁_若写锁已经被其它线程获取则当前线程无法获取读锁() throws Exception {
        // 创建读写锁
        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        Lock readLock = reentrantReadWriteLock.readLock();
        Lock writeLock = reentrantReadWriteLock.writeLock();

        // 创建CountDownLatch对象
        CountDownLatch countDownLatch = new CountDownLatch(2);

        // 创建拿写锁的任务
        Runnable writeLockRunnable = new Runnable() {
            @Override
            public void run() {
                writeLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " 获取到了写锁");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // ignore
                } finally {
                    writeLock.unlock();
                    countDownLatch.countDown();
                }
            }
        };
        // 创建拿读锁的任务
        Runnable readLockRunnable = new Runnable() {
            @Override
            public void run() {
                if (readLock.tryLock()) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " 获取到了读锁");
                    } finally {
                        readLock.unlock();
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + " 获取读锁失败");
                }
                countDownLatch.countDown();
            }
        };

        // 创建线程拿写锁
        Thread thread1 = new Thread(writeLockRunnable, "线程1");
        thread1.start();

        // 创建线程拿读锁
        Thread thread2 = new Thread(readLockRunnable, "线程2");
        thread2.start();

        countDownLatch.await();
    }

}

上述示例中,演示了线程1先获取写锁,线程2后获取读锁,并打印获取结果。运行测试程序,结果如下。

场景二_2

运行结果表明:当前线程获取读锁时,若写锁已被其它线程获取,则获取失败。

三. 场景三

本小节演示:当前线程获取写锁时,若读锁已经被获取,无论获取读锁的线程是否是当前线程,都进入等待状态。示例如下。

public class ReentrantReadWriteLockTest {

    @Test
    public void 当前线程获取写锁_若读锁已被当前线程获得则当前线程无法获取写锁() {
        // 创建读写锁
        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        Lock readLock = reentrantReadWriteLock.readLock();
        Lock writeLock = reentrantReadWriteLock.writeLock();

        // 创建CountDownLatch对象
        CountDownLatch countDownLatch = new CountDownLatch(1);

        // 创建任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 先拿读锁
                readLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " 获取到了读锁");
                    // 同一线程中再拿写锁
                    if (writeLock.tryLock()) {
                        try {
                            System.out.println(Thread.currentThread().getName() + " 获取到了写锁");
                        } finally {
                            writeLock.unlock();
                        }
                    } else {
                        System.out.println(Thread.currentThread().getName() + " 获取写锁失败");
                    }
                } finally {
                    readLock.unlock();
                    countDownLatch.countDown();
                }
            }
        };

        // 创建线程并执行任务
        Thread thread = new Thread(runnable, "线程1");
        thread.start();

        // 阻塞主线程
        countDownLatch.countDown();
    }

}

上述示例中,演示了同一线程先获取读锁,再获取写锁,并打印获取结果。运行测试程序,结果如下。

场景三

运行结果表明:当前线程获取写锁时,若读锁已经被获取,无论获取读锁的线程是否是当前线程,都获取失败。

四. 场景四

本小节演示:当前线程获取写锁时,若写锁已经被其它线程获取,则进入等待状态。示例如下。

public class ReentrantReadWriteLockTest {

    @Test
    public void 当前线程获取写锁_若写锁已经被其它线程获取则获取失败() throws Exception {
        // 创建读写锁
        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        Lock writeLock = reentrantReadWriteLock.writeLock();

        // 创建CountDownLatch对象
        CountDownLatch countDownLatch = new CountDownLatch(2);

        // 创建拿写锁的任务
        Runnable writeLockRunnable = new Runnable() {
            @Override
            public void run() {
                if (writeLock.tryLock()) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " 获取到了写锁");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // ignore
                    } finally {
                        writeLock.unlock();
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + " 获取写锁失败");
                }
                countDownLatch.countDown();
            }
        };

        // 创建线程1,拿写锁
        Thread thread1 = new Thread(writeLockRunnable, "线程1");
        thread1.start();

        // 创建线程2,拿写锁
        Thread thread2 = new Thread(writeLockRunnable, "线程2");
        thread2.start();

        // 阻塞主线程
        countDownLatch.await();
    }

}

上述示例中,演示了线程1先获取写锁,线程2后获取写锁,并打印获取结果。运行测试程序,结果如下。

场景四

上述结果表明:当前线程获取写锁时,若写锁已经被其它线程获取,则获取失败。

总结

读写锁ReentrantReadWriteLock的使用流程通常如下。

  1. 创建ReentrantReadWriteLock对象
  2. 通过ReentrantReadWriteLockreadLock()方法创建读锁ReentrantReadWriteLock.WriteLock
  3. 通过ReentrantReadWriteLockwriteLock()方法创建写锁ReentrantReadWriteLock.ReadLock
  4. 后续加读锁通过ReentrantReadWriteLock.WriteLock,加写锁通过ReentrantReadWriteLock.ReadLock

读写锁的使用规则总结如下。

  1. 当前线程获取读锁时,读锁是否被获取不会影响读锁的获取
  2. 当前线程获取读锁时,若写锁未被获取或者写锁被当前线程获取,则允许获取读锁,否则进入等待状态
  3. 当前线程获取写锁时,若读锁已经被获取,无论获取读锁的线程是否是当前线程,都进入等待状态
  4. 当前线程获取写锁时,若写锁已经被其它线程获取,则进入等待状态

读锁和写锁加锁时有两种方式。

  1. 通过lock()方法加锁,失败则进入同步队列等待
  2. 通过tryLock()方法加锁,成功返回true,失败返回false

大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 28 天,点击查看活动详情