同步与ReadWriteLock性能 [英] Synchronized vs ReadWriteLock performance

查看:132
本文介绍了同步与ReadWriteLock性能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图证明当有许多读者和只有一些作者时,synchronized会变慢。不知怎的,我证明了相反的情况。

I try to prove that synchronized is slower when there are many readers and only some writers. Somehow I proved the opposite.

RW示例,执行时间为313毫秒:

The RW example, time of execution is 313 ms:

package zad3readWriteLockPerformance;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Main {
    public static long start, end;

    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            end = System.currentTimeMillis();
            System.out.println("Time of execution " + (end - start) + " ms");
        }));
        start = System.currentTimeMillis();
        final int NUMBER_OF_THREADS = 1000;
        ThreadSafeArrayList<Integer> threadSafeArrayList = new ThreadSafeArrayList<>();
        ArrayList<Thread> consumerThreadList = new ArrayList<Thread>();
        for (int i = 0; i < NUMBER_OF_THREADS; i++) {
            Thread t = new Thread(new Consumer(threadSafeArrayList));
            consumerThreadList.add(t);
            t.start();
        }

        ArrayList<Thread> producerThreadList = new ArrayList<Thread>();
        for (int i = 0; i < NUMBER_OF_THREADS/10; i++) {
            Thread t = new Thread(new Producer(threadSafeArrayList));
            producerThreadList.add(t);
            t.start();

        }



        //  System.out.println("Printing the First Element : " + threadSafeArrayList.get(1));

    }

}
class Consumer implements Runnable {
    public final static int NUMBER_OF_OPERATIONS = 100;
    ThreadSafeArrayList<Integer> threadSafeArrayList;

    public Consumer(ThreadSafeArrayList<Integer> threadSafeArrayList) {
        this.threadSafeArrayList = threadSafeArrayList;
    }

    @Override
    public void run() {
        for (int j = 0; j < NUMBER_OF_OPERATIONS; j++) {
            Integer obtainedElement = threadSafeArrayList.getRandomElement();
        }
    }

}
class Producer implements Runnable {
    public final static int NUMBER_OF_OPERATIONS = 100;
    ThreadSafeArrayList<Integer> threadSafeArrayList;

    public Producer(ThreadSafeArrayList<Integer> threadSafeArrayList) {
        this.threadSafeArrayList = threadSafeArrayList;
    }

    @Override
    public void run() {
        for (int j = 0; j < NUMBER_OF_OPERATIONS; j++) {
            threadSafeArrayList.add((int) (Math.random() * 1000));
        }
    }

}

class ThreadSafeArrayList<E> {
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private final Lock readLock = readWriteLock.readLock();

    private final Lock writeLock = readWriteLock.writeLock();

    private final List<E> list = new ArrayList<>();

    public void add(E o) {
        writeLock.lock();
        try {
            list.add(o);
            //System.out.println("Adding element by thread" + Thread.currentThread().getName());
        } finally {
            writeLock.unlock();
        }
    }

    public E getRandomElement() {
        readLock.lock();
        try {
            //System.out.println("Printing elements by thread" + Thread.currentThread().getName());
            if (size() == 0) {
                return null;
            }
            return list.get((int) (Math.random() * size()));
        } finally {
            readLock.unlock();
        }
    }

    public int size() {
        return list.size();
    }

}

同步示例,执行时间为只有241ms:

synchronized example, time of execution is only 241ms:

package zad3readWriteLockPerformanceZMIENONENENASYNCHRO;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static long start, end;

    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            end = System.currentTimeMillis();
            System.out.println("Time of execution " + (end - start) + " ms");
        }));
        start = System.currentTimeMillis();
        final int NUMBER_OF_THREADS = 1000;
        List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
        ArrayList<Thread> consumerThreadList = new ArrayList<Thread>();
        for (int i = 0; i < NUMBER_OF_THREADS; i++) {
            Thread t = new Thread(new Consumer(list));
            consumerThreadList.add(t);
            t.start();
        }

        ArrayList<Thread> producerThreadList = new ArrayList<Thread>();
        for (int i = 0; i < NUMBER_OF_THREADS / 10; i++) {
            Thread t = new Thread(new Producer(list));
            producerThreadList.add(t);
            t.start();
        }

        //  System.out.println("Printing the First Element : " + threadSafeArrayList.get(1));

    }

}

class Consumer implements Runnable {
    public final static int NUMBER_OF_OPERATIONS = 100;
    List<Integer> list;

    public Consumer(List<Integer> list) {
        this.list = list;
    }

    @Override
    public void run() {
        for (int j = 0; j < NUMBER_OF_OPERATIONS; j++) {
            if (list.size() > 0)
                list.get((int) (Math.random() * list.size()));
        }
    }

}

class Producer implements Runnable {
    public final static int NUMBER_OF_OPERATIONS = 100;
    List<Integer> threadSafeArrayList;

    public Producer(List<Integer> threadSafeArrayList) {
        this.threadSafeArrayList = threadSafeArrayList;
    }

    @Override
    public void run() {
        for (int j = 0; j < NUMBER_OF_OPERATIONS; j++) {
            threadSafeArrayList.add((int) (Math.random() * 1000));
        }
    }

}

为何同步当我的读者比作家多十倍时,收集速度会更快。如何显示我在许多文章中读到的RW锁的进展?

Why synchronized collection is faster when I have ten times more readers than writers. How to show advance of RW locks about which I read in many articles?

推荐答案

获取ReadWriteLock的实际成本通常很高获取简单的互斥锁的成本要慢得多。 ReadWriteLock的 javadoc 去了进入:

The actual cost of acquiring a ReadWriteLock is generally much slower than the cost of acquiring a simple mutex. The javadoc for ReadWriteLock goes into this:


读写锁是否会提高使用互斥锁的性能取决于频率与被修改的数据相比,读取数据,读取和写入操作的持续时间以及数据的争用 - 即,将尝试同时读取或写入数据的线程数。例如,最初填充数据并且之后不经常修改但经常搜索的集合(例如某种目录)是使用读写锁的理想候选者。但是,如果更新变得频繁,那么数据的大部分时间都会被完全锁定,并且并发性几乎没有增加。此外,如果读取操作太短,则读写锁定实现的开销(其本质上比互斥锁定更复杂)可以支配执行成本,特别是因为许多读写锁定实现仍然通过序列化所有线程。小部分代码。最终,只有分析和测量才能确定使用读写锁是否适合您的应用。

Whether or not a read-write lock will improve performance over the use of a mutual exclusion lock depends on the frequency that the data is read compared to being modified, the duration of the read and write operations, and the contention for the data - that is, the number of threads that will try to read or write the data at the same time. For example, a collection that is initially populated with data and thereafter infrequently modified, while being frequently searched (such as a directory of some kind) is an ideal candidate for the use of a read-write lock. However, if updates become frequent then the data spends most of its time being exclusively locked and there is little, if any increase in concurrency. Further, if the read operations are too short the overhead of the read-write lock implementation (which is inherently more complex than a mutual exclusion lock) can dominate the execution cost, particularly as many read-write lock implementations still serialize all threads through a small section of code. Ultimately, only profiling and measurement will establish whether the use of a read-write lock is suitable for your application.

所以事实是你的线程执行非常简单的操作可能意味着性能主要取决于实际获取锁定所花费的时间。

So the fact that your threads are doing very simple operations may mean that performance is dominated by the amount of time spent actually acquiring the lock.

你的基准测试还存在另一个问题,那就是 Math.random 已同步。来自其 javadoc

There's another problem with your benchmarks, which is that Math.random is synchronized. From its javadoc:


此方法已正确同步,以允许多个线程正确使用。但是,如果许多线程需要以很高的速率生成伪随机数,它可能会减少每个线程争用自己的伪随机数生成器。

This method is properly synchronized to allow correct use by more than one thread. However, if many threads need to generate pseudorandom numbers at a great rate, it may reduce contention for each thread to have its own pseudorandom-number generator.

因此,即使你的并发读者在获取ReadWriteLock时没有互相阻塞,他们仍然可能会争夺在 Math.random 中获得的锁定,从而击败了一些使用ReadWriteLock的好处。您可以改为使用 ThreadLocalRandom 。此类的用法通常应为以下形式:ThreadLocalRandom.current()。nextX(...)(其中X为Int,Long等)。

So even though your concurrent readers are not blocking each other in acquiring the ReadWriteLock, they may still be contending for the lock acquired in Math.random, defeating some of the upside of using the ReadWriteLock. You can improve this by instead using ThreadLocalRandom. Usages of this class should typically be of the form: ThreadLocalRandom.current().nextX(...) (where X is Int, Long, etc).

此外,正如assylias指出的那样,没有考虑JIT编译和其他运行时怪癖的天真Java基准测试是不可靠的。您应该使用 Java Microbenchmarking Harness(JMH)来获得这些基准。

Also, as assylias points out, naive Java benchmarks which don't take into account JIT compilation and other runtime quirks are unreliable. You should use the Java Microbenchmarking Harness (JMH) for benchmarks like these.

这篇关于同步与ReadWriteLock性能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆