英文:
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 < 1000; i++)
dal1.add(i);
// Runtime.getRuntime().availableProcessors()
ExecutorService executorService = Executors.newFixedThreadPool(4);
long start = System.nanoTime();
for (int i = 0; i < 100; i++) {
executorService.execute(new Runnable() {
public void run() {
for (int i = 0; i < 1000; i++)
dal1.size();
for (int i = 0; i < 1000; i++)
dal1.get(i % 100);
}
});
}
executorService.shutdown();
try {
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
System.out.println("mayor disaster!");
}
}
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 <= i && i < 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 < 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 <= i && i < 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() { // <-- 在 "this" 上独占/互斥锁
readWriteLock.readLock().lock(); // <-- 在 readWriteLock.readLock() 上加锁
int ret = this.size.get(); // <-- 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 <= i && i < 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 < 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 <= i && i < 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 < 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() { // <-- locks exclusively/mutually on "this"
readWriteLock.readLock().lock(); // <-- locks on readWriteLock.readLock()
int ret = this.size.get(); // <-- 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 <= i && i < 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 < 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 <= i && i < 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 < 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论