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
Newsletter
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • From Database Changes to Real-Time Actions at Scale: Oracle GoldenGate and Java
  • Java 27 Makes G1 the Default Everywhere: How to Test Your Small Containers Before You Upgrade
  • Stop Writing Database Migration Scripts: Declarative Schema Sync for Java
  • Searching Kafka Without an Index: Lessons From Building Message Search for Kafdrop

Trending

  • Best of Three Worlds: AI Agents Writing In Rust and Java Beat the C Library
  • Prompt Caching for LLMs, Part 3: Model-Side Caching and Production Rollout
  • Google Cloud KMS vs. AWS KMS: Java Encryption Guide
  • Columnar Formats for AI Telemetry: Parquet vs Vortex With DuckDB

Draft This article hasn't been published, so readers can't see it.

In moderation This article is waiting for review, so readers can't see it yet.

Deleted This article has been deleted, so readers can't see it.

  1. DZone
  2. Coding
  3. Java
  4. Java Concurrency in Depth (Part 1)

Java Concurrency in Depth (Part 1)

Get your feet wet with this initial deep dive into Java concurrency, where we'll cover synchronization, the volatile keyword, and atomic classes.

By 
Mahmoud Nagib
·
Dec. 07, 17 · Tutorial
Post on X (opens in a new tab) Share on LinkedIn (opens in a new tab)
65.8K Views

Join the DZone community Save articles, join the discussion, and download Refcards and Trend Reports with a free account.

Join for free Already a member? Log in

Java comes with strong support for multi-threading and concurrency, which makes it easy to write concurrent applications. But usually, multi-threaded applications are tricky to debug, troubleshoot, and sometimes to scale. From my experience with concurrent applications, most of the issues are found when they run at scale, which means when they go live in many cases. In order to make this easier, it is better to understand how things work under the hood and the pros and cons of every choice. 

This article is the first in a series of articles discussing the internals of Java concurrency. 

Let's start with this example:

public class Foo {
    private int x;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }
}


This code is obviously not thread-safe. One way to make it thread safe is to make setX() and getX() both synchronized.

How Synchronization Works

When a thread calls a synchronized method or block, it tries to acquire an intrinsic lock (monitor). Once a thread acquires the lock, other threads block until the lock is released.

This looks okay! But there are some drawbacks for synchronization:

  1. Starvation: Synchronization doesn't guarantee fairness. This means that if there are many threads competing to acquire the lock, then there is a possibility that some threads don't get a chance to continue, which means starvation.

  2. Deadlock: Calling synchronized code from other synchronized code can cause deadlocks.

  3. Less throughput: Using synchronization means only one thread is executing on a particular object. In many cases, this is not necessary because it is enough to lock access to the variable only on write, and there no need to lock the variable if all the threads at the moment are reading (concurrent reads).

Synchronization is good for thread safety but not optimal for concurrency.

Check out this Javadoc about liveness problems.

Volatile

Another solution is using volatile.

public class Foo {
    private volatile int x;
    ...
}


How Volatile Works

Volatile is said to guarantee:

  1. Visibility: If one thread changes a value of a variable, the change will be visible immediately to other threads reading the variable. This is guaranteed by not allowing the compiler or the JVM to allocate those variables in the CPU registers. Any write to a volatile variable is flushed immediately to main memory and any read of it is fetched from main memory. That means there is a little bit of performance penalty, but that's far better from a concurrency point of view.

  2. Ordering: Sometimes for performance optimization, the JVM reorders instructions. This is not allowed when accessing volatile variables. Access to volatile variables is not reordered with access to other volatile variables, nor with access to other normal fields around them. This makes writes to non-volatile fields around them visible immediately to other threads.

Let's look at an example to clarify this:

public class Foo {
    private int x = -1;
    private volatile boolean v = false;

    public void setX(int x) {
        this.x = x;
        v = true;
    }

    public int getX() {
        if (v == true) {
            return x;
        }
        return 0;
    }
}


Because of the first rule, if thread A calls setX(), and thread B calls getX(), then the change to v will be visible immediately to thread B. And because of the second rule, the change to x will be visible to thread B immediately as well.

However, volatile is not suitable for some operations, like ++, --, etc. This is because these operations translate into multiple read and write instructions. For example:

public int increment() {
    //x++
    int tmp = x;
    tmp = tmp + 1;
    x = tmp;
    return x;
}


In a multi-threaded program, such operations should be atomic, which volatile doesn't guarantee. Java SE comes with a set of atomic classes like AtomicInteger, AtomicLong, and AtomicBoolean, which can be used to solve this problem.

How Atomic Classes Work

Java relies on machine instructions/algorithms to achieve atomicity. Prior to Java 8, Atomic classes used Compare-and-Swap. Starting in Java 8, some methods of atomic classes began using Fetch-and-Add.

Let's have a look at this implementation of AtomicInteger.getAndIncrement() in Java 7:

public final int getAndIncrement() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return current;
    }
}


In Java 8, that implementation has changed to:

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}


In the first implementation, compareAndSet returns true only if the actual value equals the current one, so the loop goes indefinitely until this condition is met.

This will be completely fine in an environment with few threads, but let's think: What if we have 100 threads calling this function? Due to the high contention, race conditions are worse — so the loop might keep going on for a long time. That could lead to a livelock situation. In such cases, solutions have to be designed carefully. One idea could be using something like a map-reduce solution, where you divide the threads into sets (mappers) and each set shares an atomic instance and a reducer thread collects values from the shared atomic instances.

Is this problem solved in Java 8?

  1. Keep in mind there are still some methods using the first approach, like getAndUpdate(IntUnaryOperator).

  2. Performance under contention still goes down, but it remains much better in Java 8. Check out this blog post where Ashkrit has plotted graphs comparing the performance of both.

In the next part, I will discuss different types of locks...

Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • From Database Changes to Real-Time Actions at Scale: Oracle GoldenGate and Java
  • Java 27 Makes G1 the Default Everywhere: How to Test Your Small Containers Before You Upgrade
  • Stop Writing Database Migration Scripts: Declarative Schema Sync for Java
  • Searching Kafka Without an Index: Lessons From Building Message Search for Kafdrop

Partner Resources

×

Comments

Loading comments

The comments didn't load

Please try again in a moment.

Likes

Loading

The next likes didn't load. Please try again.

The likes didn't load

Please try again in a moment.

See who liked this

Log in or join DZone for free to see who liked this article.

Log in Join for free
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook