Атомные переменные против синхронизированных методов - PullRequest
0 голосов
/ 21 мая 2018

У меня есть класс счетчика, у которого есть метод приращения и уменьшения, оба метода синхронизированы.

public class Counter {
   int count = 0;
   public synchronized void increment(){
       count++;
   }

   public synchronized void decrement(){
       count--;
   }
}

Из этого примера очень ясно, что условие гонки не произойдет, только один поток может получить доступ к приращениюили метод декремента.

Теперь вместо целочисленных примитивов, если я изменил класс счетчика с помощью Atomic Integer и удалил ключевое слово synchronized, можем ли мы достичь того же?

public class Counter {
    AtomicInteger count = new AtomicInteger();

    public void increment(){
       count.incrementAndGet();
    }

    public void decrement(){
       count.decrementAndGet();
    }
}

Ответы [ 2 ]

0 голосов
/ 21 мая 2018

Начиная с java-8 вместо AtomicInteger есть опция лучше (читать быстрее в действительно конфликтных средах), она называется LongAdder и гарантирует атомарные обновления втаким же образом:

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

Его реализация превосходна.Говоря простым языком (упрощенно): если нет разногласий между потоками, не будь умницей и просто работай, как с AtomicLong;если есть, попытайтесь создать отдельное рабочее пространство для каждого потока, чтобы минимизировать конфликт.

Он имеет те же методы, которые вам нужны: add и decrement.

0 голосов
/ 21 мая 2018

Ну, это вопрос такого рода, где вы можете написать быструю грязную программу для оценки:

public class ThreadExperiment {

    public static class CounterSync {
        int count = 0;
        public synchronized void increment(){
            count++;
        }

        public synchronized void decrement(){
            count--;
        }
    }

    public static class CounterAtomic {
        AtomicInteger count = new AtomicInteger();

        public  void increment(){
            count.incrementAndGet();
        }

        public void decrement(){
            count.decrementAndGet();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final CounterSync counterSync = new CounterSync();
        final CounterAtomic counterAtomic = new CounterAtomic();

        Runnable runnable = () -> {
            for (int i = 0; i < 1_000_000; i++) {
                int j = i & 1;
                if (j == 0) {
                    counterSync.increment();
                    counterAtomic.increment();
                } else {
                    counterSync.decrement();
                    counterAtomic.decrement();
                }
            }
        };

        Thread[] threads = new Thread[10];

        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(runnable);
        }

        for (Thread t : threads)
            t.start();

        for (Thread t : threads)
            t.join();

        System.out.println("Sync count = " + counterSync.count);
        System.out.println("Atomic count = " + counterAtomic.count.intValue());
    }
}

В конце оба счетчика должны и будут равны 0. Удалив "синхронизированные ключевые слова, вы увидите, что все идет совершенно не так.

Так что да, оба достигают одного и того же: безопасность потоков.

Oracle документирует пример здесь: https://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html

Для этого простого класса синхронизация является приемлемым решением.Но для более сложного класса мы могли бы избежать влияния ненужной синхронизации на жизнеспособность.Замена поля int на AtomicInteger позволяет нам предотвращать помехи потоков без обращения к синхронизации

В конце концов, поведение ваших методов будет иметь значение, если вам нужна явная синхронизация или если атомные поля будутдостаточно.

...