读/写锁在仅进行读取时比同步锁慢吗?

huangapple go评论110阅读模式
英文:

Read/write lock is slower than synchronized, even when only reading?

问题

这是代码中的部分实现:

  1. public class LongArrayListUnsafe {
  2. public static void main(String[] args) {
  3. // ...(省略部分)
  4. }
  5. class LongArrayList {
  6. private long[] items;
  7. private int size;
  8. public LongArrayList() {
  9. reset();
  10. }
  11. public static LongArrayList withElements(long... initialValues) {
  12. // ...(省略部分)
  13. }
  14. public synchronized int size() {
  15. return size;
  16. }
  17. public synchronized long get(int i) {
  18. if (0 <= i && i < size)
  19. return items[i];
  20. else
  21. throw new IndexOutOfBoundsException(String.valueOf(i));
  22. }
  23. public synchronized LongArrayList add(long x) {
  24. // ...(省略部分)
  25. }
  26. }
  27. synchronized public int size() {
  28. readWriteLock.readLock().lock();
  29. int ret = this.size.get();
  30. readWriteLock.readLock().unlock();
  31. return ret;
  32. }
  33. public long get(int i) {
  34. readWriteLock.readLock().lock();
  35. if (0 <= i && i < size.get()) {
  36. long ret = items.get(i);
  37. readWriteLock.readLock().unlock();
  38. return ret;
  39. } else {
  40. throw new IndexOutOfBoundsException(String.valueOf(i));
  41. }
  42. }
  43. }

请注意,由于篇幅限制,上述代码片段可能不是完整的,但我已经翻译了您提供的内容。如果您需要更多内容或其他帮助,请随时提问。

英文:

I have the following code ArrayList implementation

  1. public class LongArrayListUnsafe {
  2. public static void main(String[] args) {
  3. LongArrayList dal1 = LongArrayList.withElements();
  4. for (int i = 0; i &lt; 1000; i++)
  5. dal1.add(i);
  6. // Runtime.getRuntime().availableProcessors()
  7. ExecutorService executorService = Executors.newFixedThreadPool(4);
  8. long start = System.nanoTime();
  9. for (int i = 0; i &lt; 100; i++) {
  10. executorService.execute(new Runnable() {
  11. public void run() {
  12. for (int i = 0; i &lt; 1000; i++)
  13. dal1.size();
  14. for (int i = 0; i &lt; 1000; i++)
  15. dal1.get(i % 100);
  16. }
  17. });
  18. }
  19. executorService.shutdown();
  20. try {
  21. executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
  22. } catch (InterruptedException e) {
  23. System.out.println(&quot;mayor disaster!&quot;);
  24. }
  25. }
  26. class LongArrayList {
  27. private long[] items;
  28. private int size;
  29. public LongArrayList() {
  30. reset();
  31. }
  32. public static LongArrayList withElements(long...initialValues) {
  33. LongArrayList list = new LongArrayList();
  34. for (long l: initialValues)
  35. list.add(l);
  36. return list;
  37. }
  38. // Number of items in the double list
  39. public synchronized int size() {
  40. return size;
  41. }
  42. // Return item number i
  43. public synchronized long get(int i) {
  44. if (0 &lt;= i &amp;&amp; i &lt; size)
  45. return items[i];
  46. else
  47. throw new IndexOutOfBoundsException(String.valueOf(i));
  48. }
  49. // Add item x to end of list
  50. public synchronized LongArrayList add(long x) {
  51. if (size == items.length) {
  52. long[] newItems = new long[items.length * 2];
  53. for (int i = 0; i &lt; items.length; i++)
  54. newItems[i] = items[i];
  55. items = newItems;
  56. }
  57. items[size] = x;
  58. size++;
  59. return this;
  60. }

Now, this concurrent drivercode simply reads of the list, which is already made.This goes pretty fast.
But I was wondering whether it would be possible
for me to do this onlyreading operation faster with a readwritelock.
In size and get, this looks like this:

  1. synchronized public int size() {
  2. readWriteLock.readLock().lock();
  3. int ret = this.size.get();
  4. readWriteLock.readLock().unlock();
  5. return ret;
  6. }

and

  1. public long get(int i) {
  2. readWriteLock.readLock().lock();
  3. if (0 &lt;= i &amp;&amp; i &lt; size.get()) {
  4. long ret = items.get(i);
  5. readWriteLock.readLock().unlock();
  6. return ret;
  7. } else {
  8. throw new IndexOutOfBoundsException(String.valueOf(i));
  9. }
  10. }

However, using a readwritelock goes way slower, and even slower when I add more threads. Why is this? when my drivercode is only reading, the threads should have more or less unlimited acces to the methods?

答案1

得分: 0

一个 java.util.concurrent.locks.ReadWriteLock 比一个互斥锁(如 synchronized)更复杂。该类的文档中有说明。读写语义的开销可能比 return this.size;return this.items[i]; 还要大,即使有一个包围的边界检查。

我们也来具体看看你的提案。你想用以下提案替换原来的代码:

  1. public synchronized int size() {
  2. return size;
  3. }

新的提案如下:

  1. synchronized public int size() { // &lt;-- 在 "this" 上独占/互斥锁
  2. readWriteLock.readLock().lock(); // &lt;-- 在 readWriteLock.readLock() 上加锁
  3. int ret = this.size.get(); // &lt;-- size 是否是 AtomicInteger?
  4. readWriteLock.readLock().unlock();
  5. return ret;
  6. }

我假设使用 synchronized 是一个打字错误,否则它会增加另一个锁。此外,我假设 this.size.get(); 应该是 this.size;。(在这种情况下,使用 AtomicInteger 来表示 size 没有意义,并且会增加额外的成本)。如果我的假设是正确的,你的实际提案应该是:

  1. public int size() {
  2. readWriteLock.readLock().lock();
  3. int ret = this.size;
  4. readWriteLock.readLock().unlock();
  5. return ret;
  6. }
  7. public long get(int i) {
  8. readWriteLock.readLock().lock();
  9. if (0 &lt;= i &amp;&amp; i &lt; this.size) {
  10. long ret = items[i];
  11. readWriteLock.readLock().unlock();
  12. return ret;
  13. } else {
  14. throw new IndexOutOfBoundsException(String.valueOf(i));
  15. }
  16. }
  17. public LongArrayList add(long x) {
  18. readWriteLock.writeLock().lock();
  19. if (size == items.length) {
  20. long[] newItems = new long[items.length * 2];
  21. for (int i = 0; i &lt; items.length; i++)
  22. newItems[i] = items[i];
  23. this.items = newItems;
  24. }
  25. items[size] = x;
  26. size++;
  27. readWriteLock.writeLock().unlock();
  28. return this;
  29. }

get(int) 方法的实现是有风险的。如果抛出 IndexOutOfBoundException,读锁将永远保持锁定状态。这不会减慢后续的读操作,但会使所有对 add(long) 的调用等待。如果使用锁,建议将其与 finally 结合使用,以确保锁被解锁:

  1. public long get(int i) {
  2. readWriteLock.readLock().lock();
  3. try {
  4. if (0 &lt;= i &amp;&amp; i &lt; size) {
  5. return items[i];
  6. }
  7. throw new IndexOutOfBoundsException(String.valueOf(i));
  8. }
  9. finally {
  10. readWriteLock.readLock().unlock();
  11. }
  12. }
  13. public LongArrayList add(long x) {
  14. readWriteLock.writeLock().lock();
  15. try {
  16. if (size == items.length) {
  17. long[] newItems = new long[items.length * 2];
  18. for (int i = 0; i &lt; items.length; i++)
  19. newItems[i] = items[i];
  20. items = newItems;
  21. }
  22. items[size] = x;
  23. size++;
  24. }
  25. finally {
  26. readWriteLock.writeLock().unlock();
  27. }
  28. return this;
  29. }

如上所述,如果你读取的次数远远大于写入次数,使用 synchronized 可能更高效。

英文:

A java.util.concurrent.locks.ReadWriteLock is an inherently more complex thing than a mutual exclusion lock like synchronized. The documentation of the class states this. The overhead of the read-write semantics are likely bigger than return this.size;, or return this.items[i];, even with a surrounding boundary check.

Let's also look at your proposal in particular. You want to replace the original

  1. public synchronized int size() {
  2. return size;
  3. }

with the proposal

  1. synchronized public int size() { // &lt;-- locks exclusively/mutually on &quot;this&quot;
  2. readWriteLock.readLock().lock(); // &lt;-- locks on readWriteLock.readLock()
  3. int ret = this.size.get(); // &lt;-- is size and AtomicInteger now?
  4. readWriteLock.readLock().unlock();
  5. return ret;
  6. }

I assume the use of synchronized was a typo, or it would add another lock to the equation. Also, I assume this.size.get(); should be this.size;. (using an AttomicInteger for size makes no sense in this context and adds additional cost). If my assumptions are correct, your actual proposal would be:

  1. public int size() {
  2. readWriteLock.readLock().lock();
  3. int ret = this.size;
  4. readWriteLock.readLock().unlock();
  5. return ret;
  6. }
  7. public long get(int i) {
  8. readWriteLock.readLock().lock();
  9. if (0 &lt;= i &amp;&amp; i &lt; this.size) {
  10. long ret = items[i];
  11. readWriteLock.readLock().unlock();
  12. return ret;
  13. } else {
  14. throw new IndexOutOfBoundsException(String.valueOf(i));
  15. }
  16. }
  17. public LongArrayList add(long x) {
  18. readWriteLock.writeLock().lock();
  19. if (size == items.length) {
  20. long[] newItems = new long[items.length * 2];
  21. for (int i = 0; i &lt; items.length; i++)
  22. newItems[i] = items[i];
  23. this.items = newItems;
  24. }
  25. items[size] = x;
  26. size++;
  27. readWriteLock.writeLock().unlock();
  28. return this;
  29. }

The implementation of get(int) is dangerous. If an IndexOutOfBoundException is thrown, the read-lock remains locked forever. That won't slow down further reads, but it keeps all future calls to add(long) waiting. If you use a lock, it is advisable to use it in combination with finally to ensure it is unlocked:

  1. public long get(int i) {
  2. readWriteLock.readLock().lock();
  3. try {
  4. if (0 &lt;= i &amp;&amp; i &lt; size) {
  5. return items[i];
  6. }
  7. throw new IndexOutOfBoundsException(String.valueOf(i));
  8. }
  9. finally {
  10. readWriteLock.readLock().unlock();
  11. }
  12. }
  13. public LongArrayList add(long x) {
  14. readWriteLock.writeLock().lock();
  15. try {
  16. if (size == items.length) {
  17. long[] newItems = new long[items.length * 2];
  18. for (int i = 0; i &lt; items.length; i++)
  19. newItems[i] = items[i];
  20. items = newItems;
  21. }
  22. items[size] = x;
  23. size++;
  24. }
  25. finally {
  26. readWriteLock.writeLock().unlock();
  27. }
  28. return this;
  29. }

As mentioned, if you are reading far more than you write, using synchronized could be more performant.

huangapple
  • 本文由 发表于 2020年10月17日 22:00:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/64403289.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定