2016-01-05 3 views
2

Я пытаюсь доказать, что синхронизация происходит медленнее, когда есть много читателей и только некоторые авторы. Как-то я доказал обратное.Synchronized vs ReadWriteLock performance

Пример RW, время выполнения составляет 313 мс:

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:

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, о которых я читал во многих статьях?

+4

Основные сведения: http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java – assylias

+1

Стоит отметить, что 'list.get ((int) (Список Math.random() *.size())) 'не является в общем потокобезопасным даже для синхронизированного списка, поскольку другой поток может вызывать' remove' между вызовами 'size' и' get', что приводит к исключению IndexOutOfBoundsException, если первый поток пытается ' get' из предыдущего индекса. Хотя это не может быть проблемой для вашего случая использования, если 'remove' никогда не вызывается. – MikeFHay

ответ

4

Фактическая стоимость приобретения ReadWriteLock обычно намного медленнее, чем затраты на приобретение простого мьютекса. javadoc для ReadWriteLock переходит в это:

ли или нет блокировки чтения-записи позволит повысить производительность по сравнению с использованием взаимной блокировки исключения зависит от частоты, что данные считываются по сравнению с модифицируется, продолжительность операции чтения и записи и конкуренция за данные - то есть число потоков, которые будут пытаться читать или записывать данные одновременно. Например, коллекция, которая изначально заполнена данными и впоследствии редко изменена, хотя часто просматривается (например, какой-то каталог), является идеальным кандидатом на использование блокировки чтения-записи. Однако, если обновления становятся частыми, данные тратят большую часть времени исключительно на блокировку, и малое, если есть увеличение параллелизма. Кроме того, если операции чтения слишком короткие, накладные расходы на реализацию блокировки чтения и записи (которая по своей сути более сложна, чем блокировка взаимного исключения) могут доминировать над стоимостью выполнения, в частности, поскольку многие блокировки чтения и записи по-прежнему сериализуют все потоки через небольшой раздел кода. В конечном счете, только профилирование и измерение определит, подходит ли использование блокировки чтения-записи для вашего приложения.

Так что факт, что ваши потоки выполняют очень простые операции, может означать, что на производительность преобладает количество времени, затрачиваемого фактически на приобретение блокировки.

Есть еще одна проблема с вашими эталонами, которая заключается в синхронизации Math.random. От его javadoc:

Этот метод правильно синхронизирован, чтобы обеспечить правильное использование более чем одним потоком. Однако, если многие потоки должны генерировать псевдослучайные числа с большой скоростью, это может уменьшить конкуренцию для каждого потока, чтобы иметь свой собственный генератор псевдослучайных чисел.

Таким образом, даже если ваши читатели одновременно не блокируют друг друга в приобретении ReadWriteLock, они все еще могут быть претендующих на замок приобрел в Math.random, побеждая некоторые перевернутом использования ReadWriteLock. Вы можете улучшить это, используя ThreadLocalRandom. Использования этого класса обычно должны иметь вид: ThreadLocalRandom.current(). NextX (...) (где X - Int, Long и т. Д.).

Кроме того, как указывает асилия, наивные тесты Java, которые не учитывают компиляцию JIT и другие ошибки времени выполнения, ненадежны. Вы должны использовать Java Microbenchmarking Harness (JMH) для таких тестов.