并发编程三:读写锁

太过爱你忘了你带给我的痛 2022-09-25 05:16 308阅读 0赞

并发编程之读写锁


介绍

  1. 所谓读写锁指的是,对待读操作是一种逻辑对待写实另一种判断逻辑。读写锁的场景通常是读大于甚至远大于写的请求,此时使用读写锁就非常的合适。
  2. 举例:比如数据库存有一个用户信息的表记录,如果多个线程都来读取某个用户的信息,那么我们应该是允许多个线程同时操作的,
  3. 但是当有一个用户来进行更新操作时,那么这条记录就会对其它线程不可见即对该记录上锁,直到更新线程结束才释放锁资源。这也很好理解,符合人们的正常思维。

引入读写锁

资源对象

  1. /**
  2. *
  3. * @author xuyi
  4. * @Time 2016年8月13日 上午9:24:04
  5. * @类名 Resource
  6. * @功能描述:存在锁重入的方法
  7. * @春风十里不如你
  8. * @备注:
  9. */
  10. public class Resource
  11. {
  12. // 资源名字
  13. private String name;
  14. // 读取资源名字
  15. public String readName()
  16. {
  17. return getName();
  18. }
  19. //读取资源名字操作不需要进行同步
  20. // 更新资源名字
  21. public void writeName(String newName)
  22. {
  23. setName(newName);
  24. }
  25. //显然更新资源名字操作需要加锁同步
  26. public String getName()
  27. {
  28. return name;
  29. }
  30. public void setName(String name)
  31. {
  32. this.name = name;
  33. }
  34. }

  1. 假如现在是这种场景,大量的读和少量的更新操作,如果我们使用普通的锁实现方式存在的问题是如果对读上普通锁的话,
  2. 那么回导致锁竞争比较强并且当有写操作线程可能会存在锁饥饿。
  3. 所以读写锁具有这些特征:
  4. 1、允许多个线程都操作
  5. 2、写线程的优先级要高于读的线程(即当有请求写线程来申请锁资源的时候,读线程的锁资源将不会再继续生成)

  1. 对读写访问资源的条件概述:
  2. 读取-没有线程正在做写操作,且没有线程在请求写操作
  3. 写入-没有线程正在做读写操作

简单的读写锁实现

  1. /**
  2. *
  3. * @author xuyi
  4. * @Time 2016年8月13日 上午10:19:35
  5. * @类名 SimpleReadWriteLock
  6. * @功能描述:简单的读写锁实现
  7. * @春风十里不如你
  8. * @备注:
  9. */
  10. public class SimpleReadWriteLock
  11. {
  12. // 读线程数
  13. private int readers = 0;
  14. // 写线程数(在0/1之间变化)
  15. private int writers = 0;
  16. // 请求写线程数
  17. private int requestWriters = 0;
  18. // 获取读锁
  19. public void readLock()
  20. {
  21. synchronized (this)
  22. {
  23. while (writers > 0 || requestWriters > 0)
  24. {// 当存在写线程或请求写线程,那么就等待.
  25. try
  26. {
  27. wait();
  28. } catch (InterruptedException e)
  29. {
  30. }
  31. }
  32. readers++;
  33. }
  34. }
  35. // 释放读锁
  36. public void unlockReadLock()
  37. {
  38. synchronized (this)
  39. {
  40. readers--;
  41. notifyAll();
  42. }
  43. }
  44. // 获取写锁
  45. public void writeLock()
  46. {
  47. synchronized (this)
  48. {
  49. requestWriters++;
  50. while (readers > 0 || writers > 0)
  51. {// 如果存在写线程或读线程就等待
  52. try
  53. {
  54. wait();
  55. } catch (InterruptedException e)
  56. {
  57. }
  58. }
  59. requestWriters--;
  60. writers++;
  61. }
  62. }
  63. // 释放写锁
  64. public void unlockWriteLock()
  65. {
  66. synchronized (this)
  67. {
  68. writers--;
  69. notifyAll();
  70. }
  71. }
  72. }

  1. 备注:这个简单的读写锁实现也比较简单粗暴,并没有考虑重入锁的场景,我们下面来优化这个读写锁。

简单的重入读写锁实现


分析读锁写锁重入

  1. 读锁重入要满足以下一个条件
  2. 1、没有写请求和没有写操作(即最初读锁条件)
  3. 2、该线程已经持有读锁
  4. 写锁重入要满足以条件
  5. 1、该线程已经持有写锁

改造后的可重入读写锁Code

  1. /**
  2. *
  3. * @author xuyi
  4. * @Time 2016年8月13日 下午3:05:38
  5. * @类名 SimpleReentrantLockReadWriteLock
  6. * @功能描述:简单的重入读写锁实现
  7. * @春风十里不如你
  8. * @备注:
  9. */
  10. public class SimpleReentrantLockReadWriteLock
  11. {
  12. // 读线程数
  13. private int readersCount = 0;
  14. // 写线程数(在0/1之间变化)
  15. private int writersCount = 0;
  16. // 请求写线程数
  17. private int requestWriters = 0;
  18. // 持有读锁线程容器
  19. private Map<Thread, Integer> readerThreads = new HashMap<>();
  20. // 持有写锁的线程
  21. private Thread writeThread = null;
  22. // 获取读锁
  23. public void readLock()
  24. {
  25. synchronized (this)
  26. {
  27. Thread currentThread = Thread.currentThread();
  28. while (writersCount > 0 || requestWriters > 0 || !isCurrentThreadHoldReadLock(currentThread))
  29. // 上面的条件应该重构出一个方法出来
  30. {// 如果存在写线程或请求写线程,或者当前线程没有持有读锁,那么就等待.
  31. try
  32. {
  33. wait();
  34. } catch (InterruptedException e)
  35. {
  36. }
  37. }
  38. readerThreads.put(currentThread, (getCurrentThreadCount(currentThread) + 1));
  39. }
  40. }
  41. // 释放读锁
  42. public void unlockReadLock()
  43. {
  44. synchronized (this)
  45. {
  46. Thread currentThread = Thread.currentThread();
  47. int count = getCurrentThreadCount(currentThread);
  48. if (count == 1)
  49. {
  50. readerThreads.remove(currentThread);
  51. } else
  52. {
  53. readerThreads.put(currentThread, (count - 1));
  54. }
  55. notifyAll();
  56. }
  57. }
  58. /**
  59. * 当前线程是否持有读锁
  60. *
  61. * @param currentThread
  62. * @return
  63. */
  64. public boolean isCurrentThreadHoldReadLock(Thread currentThread)
  65. {
  66. return readerThreads.containsKey(currentThread);
  67. }
  68. /**
  69. * 获得当前线程上锁次数
  70. *
  71. * @param currentThread
  72. * @return
  73. */
  74. public int getCurrentThreadCount(Thread currentThread)
  75. {
  76. Integer count = readerThreads.get(currentThread);
  77. if (count == null)
  78. return 0;
  79. return count.intValue();
  80. }
  81. // 获取写锁
  82. public void writeLock()
  83. {
  84. synchronized (this)
  85. {
  86. Thread currentThread = Thread.currentThread();
  87. requestWriters++;
  88. while (readersCount > 0 || writersCount > 0 || (writeThread != null && currentThread != writeThread))
  89. // 上面这个条件应该抽成一个方法
  90. {// 如果存在写线程或读线程就等待
  91. try
  92. {
  93. wait();
  94. } catch (InterruptedException e)
  95. {
  96. }
  97. }
  98. requestWriters--;
  99. writersCount++;
  100. writeThread = currentThread;
  101. }
  102. }
  103. // 释放写锁
  104. public void unlockWriteLock()
  105. {
  106. synchronized (this)
  107. {
  108. writersCount--;
  109. if (writersCount == 0)
  110. {
  111. writeThread = null;
  112. }
  113. notifyAll();
  114. }
  115. }
  116. }

总结

  1. 虽然jdk并发包已经为我们提供了读写锁的实现,但是我们能够理解其实现原理对我们今后使用和理解有很大帮助。
  2. 和普通锁一样,不管是哪种类型锁实现,最终都要释放锁资源,所以将释放锁资源的代码块放在finally块中。

参考

1、https://www.ibm.com/developerworks/cn/linux/l-cn-rwspinlock1/
2、http://ifeve.com/read-write-locks/

发表评论

表情:
评论列表 (有 0 条评论,308人围观)

还没有评论,来说两句吧...

相关阅读

    相关 并发编程-

    前言: 读写锁一般使用的场景是:读的操作远大于写操作,只有在这种情况下,才可以增加并发性。当写的操作大于读的操作(完全违背了读写锁的定义,后面会讲到),当频繁切换锁的话,性能