Read/write lock 比 synchronized 慢,即使只读?
Read/write lock is slower than synchronized, even when only reading?
我有以下代码实现ArrayList
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;
}
现在,这个并发驱动程序代码只是读取列表,这已经 made.This 非常快了。
但我想知道这是否可能
对我来说,使用读写锁可以更快地执行此 onlyreading 操作。
在 size 和 get 上,这看起来像这样:
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));
}
}
但是,使用 readwritelock 会变慢,当我添加更多线程时甚至更慢。为什么是这样?当我的驱动程序代码只读时,线程应该或多或少地无限制地访问这些方法?
一个java.util.concurrent.locks.ReadWriteLock本质上比synchronized
这样的互斥锁更复杂。 class 的文档说明了这一点。 read-write 语义的开销可能比 return this.size;
或 return this.items[i];
大,即使有周围的边界检查。
我们也来具体看看您的提案。你要替换原来的
public synchronized int size() {
return size;
}
提议
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;
}
我假设 synchronized
的使用是一个拼写错误,或者它会给等式添加另一个锁。另外,我假设 this.size.get();
应该是 this.size;
。 (在这种情况下,使用 AttomicInteger 作为大小没有意义,并且会增加额外的成本)。如果我的假设是正确的,你的实际建议是:
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
,read-lock 将永远锁定。这不会减慢进一步的读取速度,但它会让所有对 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
可能会更高效。
我有以下代码实现ArrayList
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;
}
现在,这个并发驱动程序代码只是读取列表,这已经 made.This 非常快了。 但我想知道这是否可能 对我来说,使用读写锁可以更快地执行此 onlyreading 操作。 在 size 和 get 上,这看起来像这样:
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));
}
}
但是,使用 readwritelock 会变慢,当我添加更多线程时甚至更慢。为什么是这样?当我的驱动程序代码只读时,线程应该或多或少地无限制地访问这些方法?
一个java.util.concurrent.locks.ReadWriteLock本质上比synchronized
这样的互斥锁更复杂。 class 的文档说明了这一点。 read-write 语义的开销可能比 return this.size;
或 return this.items[i];
大,即使有周围的边界检查。
我们也来具体看看您的提案。你要替换原来的
public synchronized int size() {
return size;
}
提议
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;
}
我假设 synchronized
的使用是一个拼写错误,或者它会给等式添加另一个锁。另外,我假设 this.size.get();
应该是 this.size;
。 (在这种情况下,使用 AttomicInteger 作为大小没有意义,并且会增加额外的成本)。如果我的假设是正确的,你的实际建议是:
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
,read-lock 将永远锁定。这不会减慢进一步的读取速度,但它会让所有对 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
可能会更高效。