2015-09-06 4 views
3

Я тестирую, как быстро Atomic Integer в многопоточном режиме сравнивает синхронизированный метод, но я получил результат, что Atomic Integer был медленнее, чем синхронизированный метод.AtomicInteger работает медленнее, чем синхронизировано

Я прочитал Справочник по API Java, и я понял, что Atomic Integer быстрее, чем синхронизирован в многопоточном режиме.

Так что я хочу знать причину, по которой я получил плохой результат с Atomic Integer. Мой тестовый код был неправильным?

Вот мой код

import java.util.concurrent.atomic.AtomicInteger; 

public class AtomicThreadTest { 

    // Number of thread to run 
    private static final int THREAD_NUM = 1000; 
    // Repetition number to count up 
    private static final int ROOP_NUM = 200000; 

    // Base counter class 
    private abstract class Counter implements Runnable { 
     @Override 
     public void run() { 
      for (int i = 0; i < ROOP_NUM; i++) { 
       increment(); 
      } 
     } 
     // Increment method 
     public abstract void increment(); 
     // Get result of calculation 
     public abstract int getResult(); 
     // Get name of the class 
     @Override 
     public String toString() { 
      return getClass().getSimpleName(); 
     } 
    } 

    // Use no thread safe 
    private static int count = 0; 
    private class NoThreadSafeCounter extends Counter { 
     @Override 
     public void increment() { 
      count++; 
     } 
     @Override 
     public int getResult() { 
      return count; 
     } 
    } 

    // Use volatile 
    private static volatile int volatileCount = 0; 
    private class VolatileCounter extends Counter { 
     @Override 
     public void increment() { 
      volatileCount++; 
     } 
     @Override 
     public int getResult() { 
      return volatileCount; 
     } 
    } 

    // Use synchronized 
    private static int synchronizedCount = 0; 
    private class SynchronizedCounter extends Counter { 
     @Override 
     public synchronized void increment() { 
      synchronizedCount++; 
     } 
     @Override 
     public int getResult() { 
      return synchronizedCount; 
     } 
    } 

    // Use AtomicInteger 
    private static AtomicInteger atomicCount = new AtomicInteger(0); 
    private class AtomicCounter extends Counter { 
     @Override 
     public void increment() { 
      atomicCount.incrementAndGet(); 
     } 
     @Override 
     public int getResult() { 
      return atomicCount.get(); 
     } 
    } 

    public static void main(String[] args) { 
     AtomicThreadTest testClass = new AtomicThreadTest(); 
     Counter[] testCounter = { 
       testClass.new NoThreadSafeCounter(), 
       testClass.new VolatileCounter(), 
       testClass.new SynchronizedCounter(), 
       testClass.new AtomicCounter(), 
     }; 

     for (Counter c : testCounter) { 
      System.out.println("-------------------------"); 
      System.out.printf("Test for class : %s\n", c.toString()); 
      try { 
       test(c); 
      } catch (InterruptedException e) { 
       System.out.println("Test halted"); 
      } finally { 
       System.out.println(""); 
       System.gc(); 
      } 
     } 
     System.out.println("-------------------------"); 
    } 

    public static void test(final Counter counter) throws InterruptedException { 
     System.out.printf("Start with threads : %d, roop : %d\n", THREAD_NUM, ROOP_NUM); 
     final long startTime = System.currentTimeMillis(); 

     // Create THREAD_NUM threads and run them 
     Thread[] threads = new Thread[THREAD_NUM]; 

     for (int i = 0; i < THREAD_NUM; i++) { 
      threads[i] = new Thread(counter); 
      threads[i].start(); 
     } 

     // Wait for all threads other than this end 
     while (Thread.activeCount() > 1) { 
      Thread.sleep(10); 
     } 

     final long endTime = System.currentTimeMillis(); 
     System.out.printf("Result %d, Expected %d\n", counter.getResult(), THREAD_NUM*ROOP_NUM); 
     System.out.printf("Time to calc : %d\n", endTime-startTime); 
    } 
} 

И я получил результат ниже.

------------------------- 
Test for class : NoThreadSafeCounter 
Start with threads : 1000, roop : 200000 
Result 198785583, Expected 200000000 
Time to calc : 127 

------------------------- 
Test for class : VolatileCounter 
Start with threads : 1000, roop : 200000 
Result 19162116, Expected 200000000 
Time to calc : 4458 

------------------------- 
Test for class : SynchronizedCounter 
Start with threads : 1000, roop : 200000 
Result 200000000, Expected 200000000 
Time to calc : 8426 

------------------------- 
Test for class : AtomicCounter 
Start with threads : 1000, roop : 200000 
Result 200000000, Expected 200000000 
Time to calc : 15190 
+3

Вы не должны вызывать 'System.gc()', поскольку вы не знаете, когда он будет выполнен. Вы не используете много памяти, маловероятно, что вам когда-либо понадобится GC во время выполнения. – Dici

+2

Кстати ваша синхронизация (цикл 'while' в конце) плоха и потребляет много CPU. Вместо этого вы должны использовать что-то вроде семафора. – Dici

+0

@ Dici Спасибо за ответ. Для system.gc() я снова прочитал свой код и понял, что каждый объект Counter очень прост и потребляет очень мало воспоминаний. Поэтому мне не нужно называть 'System.gc()'. Это правильно? – user2738844

ответ

1

Несмотря на проблемы с кодом вашего тестового теста, давайте поговорим о самой проблеме многопоточности.

Если вы установили THREAD_NUM на гораздо более низкое число, например, 8 или 4, вы обнаружите, что ваш AtomicCounter немного быстрее, чем SynchronizedCounter. Запуск с 1000 потоками обойдется в два процессора на AtomicInteger CAS (сравнение и обмен), в результате чего он работает медленнее, чем synchronized.

Чтобы доказать это, вы можете реализовать LongadderCounter:

private class LongadderCounter extends Counter { 

    @Override 
    public void increment() { 
     longadder.increment(); 
    } 

    @Override 
    public int getResult() { 
     return longadder.intValue(); 
    } 
} 

Вы обнаружите, что LongadderCounter гораздо быстрее. LongAdder до AtomicInteger составляет ConcurrentHashMap до Collections.synchronizedMap(new HashMap<>()), он делит счетчик на несколько частей и выполняет CAS на каждой части, чтобы смягчить условия гонки.

+0

Я был удивлен скоростью LongAdder! Это заняло 1/15 времени с AtomicInteger. И спасибо за вежливое объяснение! – user2738844

Смежные вопросы