Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Synchronized vs ReadWriteLock performance

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

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();
    }

}

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));
        }
    }

}

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?

like image 745
majewa Avatar asked Jan 05 '16 11:01

majewa


1 Answers

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.

There's also a 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.

So even though your concurrent readers are not blocking each other once they've acquired 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.

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.

like image 161
MikeFHay Avatar answered Oct 05 '22 23:10

MikeFHay