Java
读写锁(Readers-Writer Lock)顾名思义是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的。总结来说,读写锁的特点是:读读不互斥、读写互斥、写写互斥

1、读写锁使用

在 Java 语言中,读写锁是使用 ReentrantReadWriteLock 类来实现的,其中:

  • ReentrantReadWriteLock.ReadLock 表示读锁,它提供了 lock 方法进行加锁、unlock 方法进行解锁。
  • ReentrantReadWriteLock.WriteLock 表示写锁,它提供了 lock 方法进行加锁、unlock 方法进行解锁。

它的基础使用如下代码所示:

  1. // 创建读写锁
  2. final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  3. // 获得读锁
  4. final ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
  5. // 获得写锁
  6. final ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
  7. // 读锁使用
  8. readLock.lock();
  9. try {
  10. // 业务代码...
  11. } finally {
  12. readLock.unlock();
  13. }
  14. // 写锁使用
  15. writeLock.lock();
  16. try {
  17. // 业务代码...
  18. } finally {
  19. writeLock.unlock();
  20. }

1.1 读读不互斥

多个线程可以同时获取到读锁,称之为读读不互斥,如下代码所示:

  1. // 创建读写锁
  2. final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  3. // 创建读锁
  4. final ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
  5. Thread t1 = new Thread(() -> {
  6. readLock.lock();
  7. try {
  8. System.out.println("[t1]得到读锁.");
  9. Thread.sleep(3000);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. } finally {
  13. System.out.println("[t1]释放读锁.");
  14. readLock.unlock();
  15. }
  16. });
  17. t1.start();
  18. Thread t2 = new Thread(() -> {
  19. readLock.lock();
  20. try {
  21. System.out.println("[t2]得到读锁.");
  22. Thread.sleep(3000);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. } finally {
  26. System.out.println("[t2]释放读锁.");
  27. readLock.unlock();
  28. }
  29. });
  30. t2.start();

以上程序执行结果如下:为什么要用读写锁?它有什么优点? - 图1

1.2 读写互斥

读锁和写锁同时使用是互斥的(也就是不能同时获得),这称之为读写互斥,如下代码所示:

  1. // 创建读写锁
  2. final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  3. // 创建读锁
  4. final ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
  5. // 创建写锁
  6. final ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
  7. // 使用读锁
  8. Thread t1 = new Thread(() -> {
  9. readLock.lock();
  10. try {
  11. System.out.println("[t1]得到读锁.");
  12. Thread.sleep(3000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. } finally {
  16. System.out.println("[t1]释放读锁.");
  17. readLock.unlock();
  18. }
  19. });
  20. t1.start();
  21. // 使用写锁
  22. Thread t2 = new Thread(() -> {
  23. writeLock.lock();
  24. try {
  25. System.out.println("[t2]得到写锁.");
  26. Thread.sleep(3000);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. } finally {
  30. System.out.println("[t2]释放写锁.");
  31. writeLock.unlock();
  32. }
  33. });
  34. t2.start();

以上程序执行结果如下:为什么要用读写锁?它有什么优点? - 图2

1.3 写写互斥

多个线程同时使用写锁也是互斥的,这称之为写写互斥,如下代码所示:

  1. // 创建读写锁
  2. final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  3. // 创建写锁
  4. final ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
  5. Thread t1 = new Thread(() -> {
  6. writeLock.lock();
  7. try {
  8. System.out.println("[t1]得到写锁.");
  9. Thread.sleep(3000);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. } finally {
  13. System.out.println("[t1]释放写锁.");
  14. writeLock.unlock();
  15. }
  16. });
  17. t1.start();
  18. Thread t2 = new Thread(() -> {
  19. writeLock.lock();
  20. try {
  21. System.out.println("[t2]得到写锁.");
  22. Thread.sleep(3000);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. } finally {
  26. System.out.println("[t2]释放写锁.");
  27. writeLock.unlock();
  28. }
  29. });
  30. t2.start();

以上程序执行结果如下:为什么要用读写锁?它有什么优点? - 图3

2、优点分析

  1. 提高了程序执行性能:多个读锁可以同时执行,相比于普通锁在任何情况下都要排队执行来说,读写锁提高了程序的执行性能。
  2. 避免读到临时数据:读锁和写锁是互斥排队执行的,这样可以保证了读取操作不会读到写了一半的临时数据。

    3、适用场景

    读写锁适合多读少写的业务场景,此时读写锁的优势最大。

    总结

    读写锁是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,而写锁则是互斥锁。它的完整规则是:读读不互斥、读写互斥、写写互斥。它适用于多读的业务场景,使用它可以有效的提高程序的执行性能,也能避免读取到操作了一半的临时数据。