I have a counter class which has increment and decrement method, both the methods are synchronized.
public class Counter {
int count = 0;
public synchronized void increment(){
count++;
}
public synchronized void decrement(){
count--;
}
}
Its very clear from this example that race condition won't happen, only one thread can access increment or decrement method.
Now instead of integer primitives, if I modified the counter class with Atomic Integer and removed the synchronized keyword, can we achieve the same thing ?
public class Counter {
AtomicInteger count = new AtomicInteger();
public void increment(){
count.incrementAndGet();
}
public void decrement(){
count.decrementAndGet();
}
}
Starting with java-8
there is a better (read faster in really contended environments) option instead of AtomicInteger
, it's called LongAdder
and it guarantees atomic updates in the same manner:
Under low update contention, the two classes have similar characteristics (AtomicLong). But under high contention, expected throughput of this class is significantly higher, at the expense of higher space consumption.
Its implementation is superb. In plain english (simplified): if there is no contention between threads, don’t be a smart ass and simply work as with AtomicLong; if there is, try to create a separate working space for each thread, so that contention is minimized.
It has the same methods you care about: add
and decrement
.
Well, this is the kind of question where you could write a quick 'n' dirty program to evaluate:
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());
}
}
In the end, both counters should and will be 0. By removing the "synchronized" keywords, you will see that things go completely wrong.
So yes, both achieve the same thing: thread safeness.
Oracle documents the example here: https://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html
For this simple class, synchronization is an acceptable solution. But for a more complicated class, we might want to avoid the liveness impact of unnecessary synchronization. Replacing the int field with an AtomicInteger allows us to prevent thread interference without resorting to synchronization
In the end, the behaviour of your methods will make the difference if you need explicit synchronization or if atomic fields will be enough.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With