DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Data Engineering
  3. AI/ML
  4. Java Concurrency – Part 6 : Atomic Variables

Java Concurrency – Part 6 : Atomic Variables

Baptiste Wicht user avatar by
Baptiste Wicht
·
Sep. 18, 10 · Interview
Like (2)
Save
Tweet
Share
35.82K Views

Join the DZone community and get the full member experience.

Join For Free

When data (typically a variable) can be accessed by several threads, you must synchronize the access to the data to ensure visibility and correctness.

For example, if you have a simple counter (yes, once again) :

public class Counter {
private int value;
 
public int getValue(){
return value;
}
 
public int getNextValue(){
return value++;
}
 
public int getPreviousValue(){
return value--;
}
}

This class works really well in single-threaded environment, but doesn’t work at all when several threads access the same Counter instance. If you don’t know why, read this post about synchronization. You can solve the problem using synchronized at method level :

public class SynchronizedCounter {
private int value;
 
public synchronized int getValue(){
return value;
}
 
public synchronized int getNextValue(){
return value++;
}
 
public synchronized int getPreviousValue(){
return value--;
}
}

This class now works well. But locking is not a lightweight mechanism and have several disadvantages. When several threads try to acquire the same lock, one or more threads will be suspended and they will be resumed later. When the critical section is little, the overhead is really heavy especially when the lock is often acquired and there is a lot of contention. Another disadvantage is that the other threads waiting of the lock cannot do something else during waiting and if the thread who has the lock is delayed (due to a page fault or the end of the time quanta by example), the others threads cannot take their turn.

So how to do to avoid these disadvantages ? We must use non-blocking algorithms. This algorithms don’t use blocking mechanisms and by that fact are more scalable and performing. These algorithms use low-level machine instructions which are atomic to ensure the atomicity of higher-level operations. While locking is a pessimistic approach, we can also use optimistic technique to develop algorithms. This time, we’ll detect collisions between threads in which case, the operation fails and we do something else (often retrying the same operation).

The actual processors provide several instructions that simplify greatly the implementation of these non-blocking algorithms, the most-used operation today is the compare-and-swap operation (CAS). This operation takes three parameters, the memory address, the expected current value and the new value. It atomically update the value at the given memory address if the current value is the expected, otherwise it do nothing. In both cases, the operation return the value at the address after the operation execution. So when several threads try to execute the CAS operation, one thread wins and the others do nothing. So the caller can choose to retry or to do something else. We often use this operation to implement another operation, the compare-and-set. This method makes exactly the same things as CAS but return a boolean indicating if the operation succeeded or not.

Before Java 5.0, this operation was not available directly to developers, but in Java 5.0 several atomic variables (for int, long, boolean and reference values) were added. The int and long versions also supports numeric operations. The JVM compiles these classes with the better operations provided by the hardware machine, CAS or a Java implementation of the operation using a lock. Here are the classes :

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean
  • AtomicReference

All these classes supports compare-and-set (via the compareAndSet() method) and other operations (get(), set() and getAndSet()). The setters operations are implemented using compareAndSet. These classes supports multi-threaded access and have a better scalability than synchronizing all the operations.

Here is how we can rewrite our counter using an AtomicInteger :

public class AtomicCounter {
private final AtomicInteger value = new AtomicInteger(0);
 
public int getValue(){
return value.get();
}
 
public int getNextValue(){
return value.incrementAndGet();
}
 
public int getPreviousValue(){
return value.decrementAndGet();
}
}

The incrementAndGet() and decrementAndGet() methods are two of the numeric operations provided by the AtomicLong and AtomicInteger classes. You also have getAndDecrement(), getAndIncrement(), getAndAdd(int i) and addAndGet().

This version is faster than the synchronized one and is also thread safe.

If you only have the compareAndSet(), here is how we can implement increment() method using it :

public void increment(AtomicInteger integer){
while(true){
int current = integer.get();
int next = current + 1;

if(integer.compareAndSet(current, next)){
return;
}
}
}

This seems to be complicated, but this is the cost of non-blocking algorithms. When we detect collision, we retry until the operation succeeded. This is the common schema for non-blocking algorithms.

Here is a thread-safe Stack implemented using AtomicReference :

public class Stack {
private final AtomicReference<Element> head = new AtomicReference<Element>(null);
 
public void push(String value){
Element newElement = new Element(value);
 
while(true){
Element oldHead = head.get();
newElement.next = oldHead;
 
//Trying to set the new element as the head
if(head.compareAndSet(oldHead, newElement)){
return;
}
}
}
 
public String pop(){
while(true){
Element oldHead = head.get();
 
//The stack is empty
if(oldHead == null){
return null;
}
 
Element newHead = oldHead.next;
 
//Trying to set the new element as the head
if(head.compareAndSet(oldHead, newHead)){
return oldHead.value;
}
}
}
 
private static final class Element {
private final String value;
private Element next;
 
private Element(String value) {
this.value = value;
}
}
}

It’s really more complicated than using synchronized on the two methods but also more performant if there is contention (and often even if there is no contention).

So this ends the post. To conclude, atomic variables classes are a really good way to implement non-blocking algorithms and moreover are also a very good alternative to volatile variables, because they can provide atomicity and visibility.

From http://www.baptiste-wicht.com/2010/09/java-concurrency-atomic-variables/

Java (programming language) Algorithm

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Introduction to Spring Cloud Kubernetes
  • DevOps for Developers: Continuous Integration, GitHub Actions, and Sonar Cloud
  • How To Choose the Right Streaming Database
  • Solving the Kubernetes Security Puzzle

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: