二十六. 并发实战-读写锁的使用

前言

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

}

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

在这里插入图片描述

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

再看如下示例。

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后获取读锁,并打印获取结果。运行测试程序,结果如下。

在这里插入图片描述
运行结果表明:当前线程获取读锁时,若写锁已被其它线程获取,则获取失败。

三. 场景三

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

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

樱花祭的约定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值