Java разработала несколько классов для числовых счетчиков безопасности при многопоточности. Эти классы называются атомарными классами. Некоторые из них следующие:
java.util.concurrent.atomic.AtomicBoolean;java.util.concurrent.atomic.AtomicInteger;java.util.concurrent.atomic.AtomicLong;java.util.concurrent.atomic.AtomicReference;
Ниже приведен инкрементный тест, сравнивающий значения AtomicInteger и обычные значения int в условиях многопоточности с использованием junit4;
Полный код:
пакет test.java;импорт java.util.concurrent.CountDownLatch;импорт java.util.concurrent.atomic.AtomicInteger;импорт org.junit.Assert;импорт org.junit.Before;импорт org.junit.Test;/** * Протестируйте операцию увеличения AtomicInteger и обычного значения int в многопоточности*/public class TestAtomic { // Объект приращения атомного целого числа public static AtomicInteger counter_integer; // = new AtomicInteger(0); // Переменная типа int public static int count_int = 0; @Before public void setUp() { // Выполняем начальную настройку перед запуском всех тестов counter_integer = new AtomicInteger(0) ); } @Test public void testAtomic() throws InterruptedException { // Количество созданных потоков int threadCount = 100 // Сколько раз другие дочерние потоки выполняют внутренний цикл intloopCount = 10000600 // Управление вспомогательным объектом дочернего потока (другие потоки ожидают запуска основного потока) CountDownLatch latch_1 = new CountDownLatch(1); объект основного потока Object; (основной поток ожидает завершения работы всех вспомогательных потоков, прежде чем продолжить) CountDownLatch latch_n = new CountDownLatch(threadCount) // Создание и запуск других вспомогательных потоков for (int i =); 0; i < threadCount; i++) { Thread thread = new AtomicIntegerThread(latch_1, latch_n, LoopCount); thread.start(); } long startNano = System.nanoTime(); // Разрешить запуску других ожидающих потоков latch_1.countDown () ; // Подождем завершения других потоков latch_n.await() // long endNano = System.nanoTime(); int sum = counter_integer.get(); // Assert.assertEquals("сумма не равна threadCount *loopCount, тест не пройден", sum, threadCount *loopCount("--------testAtomic() ); Ожидается, что будет равно ------------"); System.out.println("Требуется время: " + ((endNano - startNano) / (1000 * 1000)) + "мс"); ); System.out.println("threadCount = " + (threadCount) + ";"); System.out.println("loopCount = " + (loopCount) + ";"); System.out.println("sum = " + (sum) + ";"); } @Test public void testIntAdd() throws InterruptedException { // Количество созданных потоков int threadCount = 100 // Сколько раз другие дочерние потоки выполняют внутренний цикл intloopCount = 10000600 // Управление вспомогательным объектом дочернего потока (другие потоки ожидают запуска основного потока) CountDownLatch latch_1 = new CountDownLatch(1); объект основного потока Object; (основной поток ожидает завершения работы всех вспомогательных потоков, прежде чем продолжить) CountDownLatch latch_n = new CountDownLatch(threadCount) // Создание и запуск других вспомогательных потоков for (int i =); 0; i < threadCount; i++) { Thread thread = new IntegerThread(latch_1, latch_n,loopCount); thread.start(); } long startNano = System.nanoTime(); // Разрешить другим ожидающим потокам запускаться latch_1.countDown ( ); // Ожидаем завершения других потоков latch_n.await(); // long endNano = System.nanoTime(); int sum = count_int; Assert.assertNotEquals( "сумма равна threadCount *loopCount, тест testIntAdd() не пройден", sum, threadCount *loopCount); System.out.println("-------testIntAdd(); Ожидалось, что эти два значения не равны. - -------"); System.out.println("Требуется время: " + ((endNano - startNano) / (1000*1000))+ "мс"); System.out.println("threadCount = " + (threadCount) + ";"); System.out.println("loopCount = " + (loopCount) + ";"); System.out.println("sum = " + (сумма) + ";"); } // Класс потока AtomicIntegerThread расширяет поток {частный CountDownLatch latch = null; частный CountDownLatch latchdown = null; AtomicIntegerThread(CountDownLatch latch, CountDownLatch latchdown, intloopCount) { this.latch = latch; this.latchdown = latchdown; this.loopCount = loopCount; } @Override public void run() { // Ожидаем синхронизации сигнала, попробуйте { this.latch . await(); } catch (InterruptedException e) { e.printStackTrace() } // for (int i = 0; i <); LoopCount; i++) { counter_integer.getAndIncrement(); } // Уведомление уменьшается на 1 latchdown.countDown(); } } // Класс потока IntegerThread расширяет поток { Private CountDownLatch latch = null; Private CountDownLatch latchdown = null; ; public IntegerThread (CountDownLatch latch, CountDownLatch latchdown, intloopCount) {this.latch = latch; this.latchdown = latchdown; this.loopCount =loopCount; } @Override public void run() { // Ожидаем синхронизации сигнала try { this.latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } // for (int i = 0; i <loopCount; i++) { count_int++ } // Уведомление уменьшается на 1 latchdown.countDown(); } };
Результаты выполнения на обычном ПК аналогичны следующим:
---------------testAtomic(); Ожидается, что два будут равны ------------------- Затраты времени: 85366msthreadCount = 100;loopCount = 10000600;sum = 1000060000;-----------------testIntAdd() Эти два значения не должны быть равными--------------; ---- Затраты времени: 1406msthreadCount = 100;loopCount = 10000600;sum = 119428988;
Из этого видно, что разница в эффективности между операцией AtomicInteger и операцией int составляет примерно 50-80 раз. Конечно, int требует очень много времени. Это сравнение предназначено только для справки.
Если определено однопоточное выполнение, то следует использовать int, а эффективность выполнения операции int в многопоточном режиме достаточно высока и занимает всего 1,5 секунды для 1 миллиарда раз;
(Предполагая, что процессор имеет тактовую частоту 2 ГГц, двухъядерный и 4-поточный, а теоретический максимум составляет 8 ГГц, теоретически происходит 8 миллиардов тактовых циклов в секунду.
Один миллиард приращений Java int занимает 1,5 секунды, что составляет 12 миллиардов операций. По расчетам, каждый цикл потребляет 12 циклов ЦП;
Лично я считаю, что эффективность хорошая. Язык C также должен требовать более 4 тактов (решение, выполнение внутреннего кода, автоматическое приращение, переход).
Идея такова: JVM и ЦП не оптимизированы радикально.
)
На самом деле эффективность AtomicInteger не низкая. Один миллиард раз занимает 80 секунд, а один миллион раз — это около одной тысячной, 80 миллисекунд.