原子变量与同步方法

我有一个计数器类,它具有递增和递减方法,这两种方法都是同步的。

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

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

从此示例中可以非常清楚地看出,争用条件不会发生,只有一个线程可以访问递增或递减方法。

现在,如果我用原子整数修改计数器类并删除同步关键字,我们可以实现同样的事情吗?

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

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

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

答案 1

首先有一个更好的(在真正有争议的环境中读取得更快)选项,而不是,它被称为LongAdder,它以相同的方式保证原子更新:java-8AtomicInteger

在低更新争用下,这两个类具有相似的特征(AtomicLong)。但在高争用下,此类的预期吞吐量明显更高,但代价是空间消耗较高。

它的实施是一流的。用简单的英语(简化):如果线程之间没有争用,就不要成为一个聪明的屁股,而只是像AtomicLong一样工作;如果有,请尝试为每个线程创建单独的工作空间,以便最大程度地减少争用。

它具有您关心的相同方法:和 。adddecrement


答案 2

好吧,这是一个你可以写一个快速的“n”脏程序来评估的问题:

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

对于这个简单的类,同步是一个可接受的解决方案。但对于更复杂的类,我们可能希望避免不必要的同步对活动性的影响。用 AtomicInteger 替换 int 字段可以防止线程干扰,而无需诉诸同步

最后,如果您需要显式同步或原子场就足够了,则方法的行为将有所不同。