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

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

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

问题

这是代码中的部分实现:

public class LongArrayListUnsafe {
    public static void main(String[] args) {
        // ...(省略部分)
    }

    class LongArrayList {
        private long[] items;
        private int size;

        public LongArrayList() {
            reset();
        }

        public static LongArrayList withElements(long... initialValues) {
            // ...(省略部分)
        }

        public synchronized int size() {
            return size;
        }

        public synchronized long get(int i) {
            if (0 <= i && i < size)
                return items[i];
            else
                throw new IndexOutOfBoundsException(String.valueOf(i));
        }

        public synchronized LongArrayList add(long x) {
            // ...(省略部分)
        }
    }

    synchronized public int size() {
        readWriteLock.readLock().lock();
        int ret = this.size.get();
        readWriteLock.readLock().unlock();
        return ret;
    }

    public long get(int i) {
        readWriteLock.readLock().lock();
        if (0 <= i && i < size.get()) {
            long ret = items.get(i);
            readWriteLock.readLock().unlock();
            return ret;
        } else {
            throw new IndexOutOfBoundsException(String.valueOf(i));
        }
    }
}

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

英文:

I have the following code ArrayList implementation

public class LongArrayListUnsafe {
public static void main(String[] args) {
LongArrayList dal1 = LongArrayList.withElements();
for (int i = 0; i &lt; 1000; i++)
dal1.add(i);
// Runtime.getRuntime().availableProcessors()
ExecutorService executorService = Executors.newFixedThreadPool(4);
long start = System.nanoTime();
for (int i = 0; i &lt; 100; i++) {
executorService.execute(new Runnable() {
public void run() {
for (int i = 0; i &lt; 1000; i++)
dal1.size();
for (int i = 0; i &lt; 1000; i++)
dal1.get(i % 100);
}
});
}
executorService.shutdown();
try {
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
System.out.println(&quot;mayor disaster!&quot;);
}
}
class LongArrayList {
private long[] items;
private int size;
public LongArrayList() {
reset();
}
public static LongArrayList withElements(long...initialValues) {
LongArrayList list = new LongArrayList();
for (long l: initialValues)
list.add(l);
return list;
}
// Number of items in the double list
public synchronized int size() {
return size;
}
// Return item number i
public synchronized long get(int i) {
if (0 &lt;= i &amp;&amp; i &lt; size)
return items[i];
else
throw new IndexOutOfBoundsException(String.valueOf(i));
}
// Add item x to end of list
public synchronized LongArrayList add(long x) {
if (size == items.length) {
long[] newItems = new long[items.length * 2];
for (int i = 0; i &lt; items.length; i++)
newItems[i] = items[i];
items = newItems;
}
items[size] = x;
size++;
return this;
}

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:

synchronized public int size() {
readWriteLock.readLock().lock();
int ret = this.size.get();
readWriteLock.readLock().unlock();
return ret;
}

and

public long get(int i) {
readWriteLock.readLock().lock();
if (0 &lt;= i &amp;&amp; i &lt; size.get()) {
long ret = items.get(i);
readWriteLock.readLock().unlock();
return ret;
} else {
throw new IndexOutOfBoundsException(String.valueOf(i));
}
}

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]; 还要大,即使有一个包围的边界检查。

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

public synchronized int size() {
return size;
}

新的提案如下:

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

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

public int size() {
readWriteLock.readLock().lock();
int ret = this.size; 
readWriteLock.readLock().unlock();
return ret;
}
public long get(int i) {
readWriteLock.readLock().lock();
if (0 &lt;= i &amp;&amp; i &lt; this.size) {
long ret = items[i];
readWriteLock.readLock().unlock();
return ret;
} else {
throw new IndexOutOfBoundsException(String.valueOf(i));
}
}
public LongArrayList add(long x) {
readWriteLock.writeLock().lock();
if (size == items.length) {
long[] newItems = new long[items.length * 2];
for (int i = 0; i &lt; items.length; i++)
newItems[i] = items[i];
this.items = newItems;
}
items[size] = x;
size++;
readWriteLock.writeLock().unlock();
return this;
}

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

public long get(int i) {
readWriteLock.readLock().lock();
try {
if (0 &lt;= i &amp;&amp; i &lt; size) {
return items[i];
} 
throw new IndexOutOfBoundsException(String.valueOf(i));
}
finally {
readWriteLock.readLock().unlock();
}
}
public LongArrayList add(long x) {
readWriteLock.writeLock().lock();
try {
if (size == items.length) {
long[] newItems = new long[items.length * 2];
for (int i = 0; i &lt; items.length; i++)
newItems[i] = items[i];
items = newItems;
}
items[size] = x;
size++;
}
finally {
readWriteLock.writeLock().unlock();
}
return this;
}

如上所述,如果你读取的次数远远大于写入次数,使用 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

public synchronized int size() {
return size;
}

with the proposal

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

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:

public int size() {
readWriteLock.readLock().lock();
int ret = this.size; 
readWriteLock.readLock().unlock();
return ret;
}
public long get(int i) {
readWriteLock.readLock().lock();
if (0 &lt;= i &amp;&amp; i &lt; this.size) {
long ret = items[i];
readWriteLock.readLock().unlock();
return ret;
} else {
throw new IndexOutOfBoundsException(String.valueOf(i));
}
}
public LongArrayList add(long x) {
readWriteLock.writeLock().lock();
if (size == items.length) {
long[] newItems = new long[items.length * 2];
for (int i = 0; i &lt; items.length; i++)
newItems[i] = items[i];
this.items = newItems;
}
items[size] = x;
size++;
readWriteLock.writeLock().unlock();
return this;
}

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:

public long get(int i) {
readWriteLock.readLock().lock();
try {
if (0 &lt;= i &amp;&amp; i &lt; size) {
return items[i];
} 
throw new IndexOutOfBoundsException(String.valueOf(i));
}
finally {
readWriteLock.readLock().unlock();
}
}
public LongArrayList add(long x) {
readWriteLock.writeLock().lock();
try {
if (size == items.length) {
long[] newItems = new long[items.length * 2];
for (int i = 0; i &lt; items.length; i++)
newItems[i] = items[i];
items = newItems;
}
items[size] = x;
size++;
}
finally {
readWriteLock.writeLock().unlock();
}
return this;
}

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:

确定