Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Atomicity Issues in Multithreading and Their Solutions

Notes May 10 3

Atomicity

An atomic operation is one that is executed as a single, indivisible unit. The operation either completes entirely or not at all.

Atomicity Issues in Multithreading

Consider a scenario where we want 100 threads to each deliver 100 flowers to Xiao Ha, but we find that the total is less than 10,000 flowers.

Root Cause Analysis

The count++ operation consists of the following steps:

  1. The thread copies the shared variable to its stack
  2. The thread increments the copied value
  3. The thread assigns the copied value back to the shared variable in memory

If Thread A is between steps 2 and 3 when Thread B performs step 1, then both threads will increment count from the same initial value. This results in count being incremented twice from the same base value, meaning one flower was not delivered.

Example Code

public class FlowerDelivery implements Runnable {
    private int count = 0;
    
    @Override
    public void run() {
        for(int i = 0; i < 100; i++) {
            count++;
            System.out.println("Delivering flower #" + count + " to Xiao Ha");
        }
    }
}

public class DeliveryTest {
    public static void main(String[] args) {
        FlowerDelivery deliveryTask = new FlowerDelivery();
        
        for(int i = 0; i < 100; i++) {
            new Thread(deliveryTask).start();
        }
    }
}

Solution 1: AtomicInteger

The AtomicInteger class provides atomic operations that ensure the consistency of count++.

import java.util.concurrent.atomic.AtomicInteger;

public class FlowerDelivery implements Runnable {
    private AtomicInteger count = new AtomicInteger(0);
    
    @Override
    public void run() {
        for(int i = 0; i < 100; i++) {
            System.out.println("Delivering flower #" + count.incrementAndGet() + " to Xiao Ha");
        }
    }
}

public class DeliveryTest {
    public static void main(String[] args) {
        FlowerDelivery deliveryTask = new FlowerDelivery();
        
        for(int i = 0; i < 100; i++) {
            new Thread(deliveryTask).start();
        }
    }
}

Solution 2: synchronized

Use synchronized blocks to lock the code that accesses shared resources, ensuring only one thread can operate on the shared data at a time.

public class FlowerDelivery implements Runnable {
    private int count = 0;
    
    @Override
    public void run() {
        synchronized(this) {
            for(int i = 0; i < 100; i++) {
                count++;
                System.out.println("Delivering flower #" + count + " to Xiao Ha");
            }
        }
    }
}

public class DeliveryTest {
    public static void main(String[] args) {
        FlowerDelivery deliveryTask = new FlowerDelivery();
        
        for(int i = 0; i < 100; i++) {
            new Thread(deliveryTask).start();
        }
    }
}

Principle Behind AtomicInteger

Spin + CAS Algorithm

The CAS algorithm involves three operands:

  1. Memory value: The shared data to be modified
  2. Expected value: The value read from memory
  3. New value: The result after performing the operation on the expected value

The algorithm works as follows:

  • If the expected value matches the current memory value, it means no other thread has modified the shared data. In this case, the memory value is updated to the new value, and the operation succeeds.
  • If the expected value doesn't match the current memory value, it means another thread has modified the shared data. In this case, the operation fails, and a new value is read from memory to retry the process. This retry mechanism is called spinning.

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Collecting Disk Capacity and Free Space Metrics in SQL Server

Monitoring storage consumption is a routine operational task. SQL Server exposes several way to inspect disk space, ranging from quick checks to more complete inventories that include total capacity....

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.