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可能会更高效。