In Java, apart from using synchronized keyword to make thread safe of a shared resource, there are other ways, using atomic classes is one of them.
What You Need
- About 9 minutes
- A favorite text editor or IDE
- Java 8 or later
1. CAS (Compare And Swap)
CAS (Compare And Swap) is a concurrent programming technique.
It compares an expected value to the actual value of the variable and modifies it only if it matches.
It is like the value of a variable is 1, and we want to change it to 2.
In a multithreaded environment, we know that others might be working on the same variable.
So we should first check if the value of the variable is 1 as we thought, if yes then we change it to 2.
If we see that the variable is 3 now, then that means someone else is working on it and so let us not touch it at this time.
So when multiple threads attempt to update the same value through CAS, one of them wins and updates the value.
And the other threads do not get suspended unlike in the case of using synchronized keyword to lock.
2. Atomic Classes
Atomic Classes use CAS to ensure thread safe of a shared resource.
The most commonly used atomic classes are : AtomicInteger, AtomicBoolean, AtomicReference and AtomicStampedReference.
These classes represent an int, boolean, and object reference respectively which can be atomically updated.
The compareAndSet method of those atomic classes is an implementation of CAS.
2.1 AtomicInteger
AtomicInteger represents an int value that may be updated atomically.
Below is an example of basic usage of AtomicInteger’s compareAndSet method.
import java.util.concurrent.atomic.AtomicInteger;
public class CasWithAtomicInteger {
public static void main(String[] args) {
AtomicInteger val = new AtomicInteger(0);
System.out.println();
System.out.println("Previous value: " + val.get());
boolean res = val.compareAndSet(0, 6);
checkResult(val, res);
System.out.println("Previous value: " + val.get());
res = val.compareAndSet(0, 0);
checkResult(val, res);
}
private static void checkResult(AtomicInteger val, boolean res) {
if (res) {
System.out.println("The value was updated and it is " + val.get());
} else {
System.out.println("The value was not updated");
}
System.out.println();
}
}
Below is the output of above code snippet :
Previous value: 0
The value was updated and it is 6
Previous value: 6
The value was not updated
An AtomicInteger is used in applications such as atomically incremented counters.
Below is an example using compareAndSet method of AtomicInteger to make counter object thread safe instead of using synchronized keyword on its inc method.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class CasWithAtomicInteger2 {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(10);
Counter counter = new Counter();
for (int i = 0; i < 1000; i++) {
service.submit(counter::inc);
}
service.shutdown();
service.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("sum = " + counter.sum.get());
}
private static class Counter {
private AtomicInteger sum = new AtomicInteger(0);
void inc() {
while (true) {
int expectedValue = sum.get();
int newValue = expectedValue + 1;
if (sum.compareAndSet(expectedValue, newValue)) {
return;
}
}
}
}
}
Above code snippet has below output :
sum = 1000
As you can see that we retry the compareAndSet operation on failure, in fact, getAndIncrement method of AtomicInteger does the same.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class CasWithAtomicInteger3 {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(10);
Counter counter = new Counter();
for (int i = 0; i < 1000; i++) {
service.submit(counter::inc);
}
service.shutdown();
service.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("sum = " + counter.sum.get());
}
private static class Counter {
private AtomicInteger sum = new AtomicInteger(0);
void inc() {
this.sum.getAndIncrement();
}
}
}
Below is the output of above code snippet :
sum = 1000
2.2 AtomicBoolean
AtomicBoolean represents a boolean value that may be updated atomically.
Below is an example using compareAndSet method of AtomicBoolean to make counter object thread safe instead of using synchronized keyword on its inc method.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class CasWithAtomicBoolean {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(10);
Counter counter = new Counter();
for (int i = 0; i < 1000; i++) {
service.submit(counter::inc);
}
service.shutdown();
service.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("sum = " + counter.sum);
}
private static class Counter {
private int sum = 0;
private AtomicBoolean lock = new AtomicBoolean(false);
void inc() {
while (true) {
if (lock.compareAndSet(false, true)) {
this.sum++;
lock.set(false);
return;
}
}
}
}
}
Below is the output of above code snippet :
sum = 1000
2.3 AtomicReference
AtomicReference provides an object reference variable which can be read and written atomically.
Below is an example using compareAndSet method of AtomicReference to make counter object thread safe instead of using synchronized keyword on its inc method.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class CasWithAtomicReference {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(10);
Counter counter = new Counter();
for (int i = 0; i < 1000; i++) {
service.submit(counter::inc);
}
service.shutdown();
service.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("sum = " + counter.sum.get());
}
private static class Counter {
private AtomicReference<Integer> sum = new AtomicReference<>(Integer.valueOf(0));
void inc() {
while (true) {
Integer expectedValue = sum.get();
Integer newValue = expectedValue + 1;
if (sum.compareAndSet(expectedValue, newValue)) {
return;
}
}
}
}
}
The output of above code snippet is below :
sum = 1000
2.4 AtomicStampedReference
The CAS mode is optimistic locking, and synchronized is pessimistic locking.
But there is also a problem with using the CAS method.
2.4.1 ABA Problem
When operating a value, CAS needs to check whether the value has changed, such as updating if there is no change.
But if a value was originally A, became B, and then became A, then when using CAS to check, it will found that its value has not changed, but it has actually changed.
The solution to the ABA problem is to use a version number.
Append a version number in front of the variable, and add 1 to the version number each time the variable is updated, then A -> B -> A will become 1A -> 2B -> 3A.
2.4.2 ABA Problem Solution In Java
JDK’s Atomic package provides a class AtomicStampedReference to solve the ABA problem.
AtomicStampedReference provides both an object reference variable and a stamp (a timestamp or a version number) that we can read and write atomically.
Below is an example using compareAndSet method of AtomicStampedReference to make counter object thread safe instead of using synchronized keyword on its inc method.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CasWithAtomicStampedReference {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(10);
Counter counter = new Counter();
for (int i = 0; i < 1000; i++) {
service.submit(counter::inc);
}
service.shutdown();
service.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("sum = " + counter.sum.getReference());
}
private static class Counter {
private AtomicStampedReference<Integer> sum = new AtomicStampedReference<>(Integer.valueOf(0), 0);
void inc() {
while (true) {
Integer expectedValue = sum.getReference();
Integer newValue = expectedValue + 1;
int expectedStamp = sum.getStamp();
int newStamp = expectedStamp + 1;
if (sum.compareAndSet(expectedValue, newValue, expectedStamp, newStamp)) {
return;
}
}
}
}
}
Below is the output of above code snippet :
sum = 1000
2.5 AtomicReferenceFieldUpdater
AtomicReferenceFieldUpdater is a reflection-based utility that enables atomic updates to designated volatile reference fields of designated classes.
It is generally used when one or both of the following are true :
- You generally want to refer to the variable normally (without having to always refer to it via the get or set methods on the atomic classes), but occasionally need an atomic get or set operation (which you can not do with a normal volatile field);
- You are going to create a large number of objects of the given type, and don’t want every single instance to have an extra object embedded in it just for atomic access in order to save memory.
Below is an example using compareAndSet method of AtomicReferenceFieldUpdater to make counter object thread safe instead of using synchronized keyword on its inc method.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class CasWithAtomicReferenceFieldUpdater {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(10);
Counter counter = new Counter();
for (int i = 0; i < 1000; i++) {
service.submit(counter::inc);
}
service.shutdown();
service.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("sum = " + counter.count);
}
private static class Counter {
volatile int count;
AtomicIntegerFieldUpdater<Counter> countFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Counter.class,
"count");
void inc() {
while (true) {
int expected = this.count;
int update = expected + 1;
if (countFieldUpdater.compareAndSet(this, expected, update)) {
return;
}
}
}
}
}
The output of above code snippet is below :
sum = 1000
2.6 AtomicMarkableReference
AtomicMarkableReference can be used to update a reference variable and a boolean flag atomically together.
A common usage of AtomicMarkableReference consists of using the boolean flag inside a data structure to mark the referenced variable has been deleted or initialized.
Below is an example without using AtomicMarkableReference, since the updates of boolean flag and referenced variable are not done atomically, it may come into a not logical situation.
import java.util.concurrent.TimeUnit;
public class CasWithAtomicMarkableReference2 {
public static void main(String[] args) throws InterruptedException {
Document doc = new Document("hello");
new Thread(doc::delete).start();
sleep(1);
if (!doc.deleted && doc.content == null) {
System.err.println("This situation should never happened!");
}
if (doc.deleted && doc.content == null) {
System.out.println("Document has been deleted");
}
}
private static void sleep(int seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static class Document {
private boolean deleted = false;
private String content;
Document(String content) {
this.content = content;
}
void delete() {
this.content = null;
sleep(2);
this.deleted = true;
}
}
}
Below is the output of above code snippet :
This situation should never happened!
Below is the solution of using AtomicMarkableReference for above problem.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;
public class CasWithAtomicMarkableReference3 {
public static void main(String[] args) throws InterruptedException {
Document doc = new Document("hello");
new Thread(doc::delete).start();
sleep(1);
if (!doc.isDeleted() && doc.read() == null) {
System.err.println("This situation should never happened!");
}
if (doc.isDeleted() && doc.read() == null) {
System.out.println("Document has been deleted");
}
}
private static void sleep(int seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static class Document {
private AtomicMarkableReference<String> content = new AtomicMarkableReference<>(null, false);
Document(String content) {
this.content.set(content, false);
}
void delete() {
while (true) {
String expectedReference = this.content.getReference();
String newReference = null;
boolean expectedMark = this.content.isMarked();
boolean newMark = true;
if (this.content.compareAndSet(expectedReference, newReference, expectedMark, newMark)) {
return;
}
}
}
boolean isDeleted() {
return this.content.isMarked();
}
String read() {
return this.content.getReference();
}
}
}
The output of above code snippet is below :
Document has been deleted
2.7 Matters that need attention when using Atomic Classes
Atomic Classes are based on CAS which is an optimistic lock unlike synchronized keyword which is pessimistic lock.
Therefore, using Atomic Classes to solve concurrency problems usually has better performance.
However, the compareAndSet method of Atomic Classes need to be placed into a infinite loop, which is also called spin lock.
If the spin is unsuccessful for a long time, it will bring a very large execution overhead to the CPU.