Concepts / Atomic Operations in Java

Atomic Operations in Java

Threads may cache shared data, so one thread can keep seeing an old value after another thread has changed it.

  • Programming

A Flag That Seems Safe to Share

Imagine two threads in one Java program. One thread sets a flag, and the other thread checks that flag over and over to decide when to stop working. The flag is shared data, and the obvious question is whether it needs protection. Synchronization is the usual tool for coordinating threads that touch shared data, but it has a real cost. Programmers are therefore tempted to skip it when they believe a simple read or write is harmless.

This article examines one specific case: when a shared variable can be used safely without synchronization, and what the volatile keyword does to make that possible. It also covers a second topic that appears in the same course material, the arithmetic operators of Java, because arithmetic results depend on both the operator and the types of the operands.

Local Copies and Stale Values

Threads in Java are allowed to cache shared data. Each thread may keep its own local copy of a shared variable. When one thread changes the variable, the copies held by other threads are not updated right away. A thread reading from its local copy can therefore keep seeing the old value for some time, even though another thread has already written a new one.

Thread Awrites the new valueShared variableholds the new valueThread B local copystill holds the old valueThread Breads the old value1. writes new value2. copy not yet updated3. reads local copy
How can Thread B keep reading an old value of a shared variable after Thread A has written a new one, and where does the stale copy live? Read the steps from top to bottom: Thread A writes, the change has not yet reached Thread B's local copy, and Thread B reads the old value.

The stale value is a visibility problem. It is distinct from the timing problems that synchronization addresses. The issue is not that two threads run at the same moment and collide. The issue is that a change made in one thread may not reach the other in time to matter. The source describes this as a subtle problem that arises when a value is set in one thread and used in another.

Making Changes Visible with volatile

Declaring a variable volatile makes changes to it immediately visible to all threads, because no thread keeps a private cached copy. The source explains that this is what makes it safe for threads to refer to volatile shared variables even outside of synchronized code. The keyword is placed on the declaration of a variable that several threads can reach, such as a field of an object shared between them.

java
Notes on the code
  • volatile on stopRequested asks Java to make each write visible to every thread that reads the flag
  • requestStop() may be called by one thread while runLoop() runs in another
  • The loop reads the flag on each pass rather than relying on a private copy

In this flag example, the volatile keyword addresses exactly the problem described earlier. Once one thread sets stopRequested to true, the other thread's loop is able to see that change without waiting for a stale copy to be refreshed. Volatile solves visibility for this single flag. It does not, by itself, make every operation on shared data safe, as the next section shows.

Where volatile Stops: Read-Modify-Write

Volatile solves visibility, but not every problem with shared data. The source gives a direct example: an increment on a variable can still be interrupted by another thread, even if that variable is volatile. An increment is not a single step. It is a read, then an add, then a write. Another thread can run between those steps, so two threads may read the same starting value and both write back the same result. Volatile does not prevent that interleaving.

java
Notes on the code
  • The volatile keyword makes each read and write of count visible to all threads
  • count++ is still three steps, so the three steps from two threads can interleave
Thread 1runs count++Shared countstarts at 0Thread 2runs count++1. reads 02. reads 03. writes 14. writes 1
When two threads each execute count++ at the same time, in what interleaved order of reads and writes is one update lost? Both threads read 0, so both write 1, and the second increment is lost even though count was volatile.

Choosing Between volatile and Synchronization

Both approaches let threads share data safely in some situations, but they do different jobs and cost different amounts. The source states that synchronization is fairly expensive computationally and that excessive use should be avoided. It also states that access to volatile variables is less efficient than access to non-volatile variables, but more efficient than using synchronization.

PropertyvolatileSynchronization
Main purposeMakes changes visible to all threadsCoordinates threads that touch shared data
Prevents read-modify-write races such as count++No, per the sourceYes, as the usual coordination tool
Relative costMore costly than non-volatile access, cheaper than synchronizationFairly expensive computationally; excessive use should be avoided
Usable outside synchronized code for visibilityYes, per the sourceNot applicable in the same way

volatile and synchronization solve different problems at different costs.

Arithmetic Operators at a Glance

When Java evaluates an arithmetic expression, it must determine both which operation to perform and what types the operands should have while performing it. The operators look familiar from mathematics. The basic binary operators are addition (+), subtraction (-), multiplication (*), division (/), and remainder (%). Unlike the others in this list, the remainder operator is covered in its own section below because its meaning is easy to misread.

The operand types can change the result, especially with division. The rest of this article shows why an expression such as 7 / 2 can give a different answer from 7 / 2.0, and how Java decides what type a mixed expression should use.

Integer Division Versus Real Division

Division follows the type of its operands. Two integer operands produce an integer result, even when ordinary mathematical division would include a fractional part. If at least one operand is a real number, Java converts the other operand to a real number and produces a real-number result. Integer division discards the fractional part, while a real operand causes real-number division.

7 and 2both int operands7 and 2.0one real operand3fractional part discarded3.5fractional part keptinteger divisionreal division
Why does 7 / 2 produce 3 while 7 / 2.0 produces 3.5, and what happens to the fractional part in each case? In the integer column the fractional part is discarded; in the real column the operand 2.0 makes the result real.
java
Output
3
3.5
1
Notes on the code
  • Both operands are int, so the fractional part is discarded
  • The operand 2.0 is a real number, so the division is real-number division
  • The remainder of 7 divided by 2 is 1

What do you think happens?

What does the expression 7 / 2 produce in Java?

  • 3.5

  • 3

  • 4

Reveal answer
Answer:

3

Both operands are integers, so the result is an integer and the fractional part (0.5) is discarded. Writing 7 / 2.0 instead makes one operand real and yields 3.5.

Type Conversion in Mixed Expressions

Suppose an expression combines values whose types differ. Before carrying out the operation, Java converts one value so that the two operands match. This automatic adjustment is called type conversion. The source gives the example 37.4 + 10. The integer 10 is converted to the real number 10.0, and Java then evaluates the operation using 37.4 and 10.0.

Worked example

Predicting the type used by 37.4 + 10

Determine which operand types Java uses to evaluate 37.4 + 10 and what kind of result it produces.

  1. 1
    Identify the operand types

    37.4 is a real number and 10 is an integer.

  2. 2
    Convert the mismatched operand

    Because the operand types differ, Java converts the integer 10 to the real number 10.0.

  3. 3
    Evaluate with matching types

    The addition is carried out on 37.4 and 10.0, both real numbers.

  4. 4
    Read the result type

    Since the operation used real-number operands, the result is a real number.

Result

The expression is evaluated as 37.4 + 10.0, producing a real-number result.

Remainder and Unary Operators

The percent sign, %, does not mean percentage in this context. It gives what remains after one number is divided by another. For integer operands, 7 % 2 is 1, 34577 % 100 is 77, and 50 % 8 is 2. Each of these is the leftover after the whole-number division has been carried out.

Most arithmetic operators combine two values. Unary operators are different because they act on one operand. Unary minus changes a number to its negative: -X has the same value as (-1) * X. Unary plus is also available, but +X leaves the value of X unchanged.

java
Notes on the code
  • The remainder operator acts on the two operands on either side of %
  • Unary minus acts on the single operand x
  • Unary plus returns the same value that x already has

The Plus Operator: Addition or Text

The plus operator has a second role in Java. When one side of the operation is a String, + concatenates the value onto that text instead of performing numeric addition. The other value is automatically converted to a String for this combination. Java decides at each step of an expression whether the + it reaches is numeric addition or text joining, based on the operands present at that step.

What do you think happens?

What does the expression 1 + 2 + "x" + 3 + 4 produce?

  • 10x34

  • 3x34

  • 3x7

  • 1 2x34

Reveal answer
Answer:

3x34

The additions are evaluated left to right. 1 + 2 is numeric addition giving 3. Then 3 + "x" has a String operand, so it concatenates to "3x". Then "3x" + 3 gives "3x3", and "3x3" + 4 gives "3x34".

Worked example

Tracing the plus operator step by step

Trace how Java evaluates 1 + 2 + "x" + 3 + 4.

  1. 1
    1 + 2

    Both operands are numbers, so + performs addition and gives 3.

  2. 2
    3 + "x"

    One operand is a String, so + concatenates. The 3 is converted to text, giving "3x".

  3. 3
    "3x" + 3

    The left side is a String, so the 3 is converted to text and appended, giving "3x3".

  4. 4
    "3x3" + 4

    Again one side is a String, so the 4 is appended, giving "3x34".

Result

The expression produces the String "3x34".

Mistakes Beginners Make

  • Common mistake

    Assuming volatile makes every operation on a shared variable safe

    What it looks like

    A volatile int counter incremented by count++ from several threads

    Why it fails

    Volatile guarantees visibility of each read and write, but count++ is a read, an add, and a write. Another thread can run between those steps.

    Do this instead

    Treat volatile as a visibility tool, and use synchronization or another coordination mechanism when a multi-step update must not be interrupted.

  • Common mistake

    Expecting 7 / 2 to produce 3.5

    What it looks like
    System.out.println(7 / 2) prints 3
    Why it fails

    Two integer operands produce an integer result, so the fractional part is discarded.

    Do this instead

    Use a real operand, as in 7 / 2.0, if a real-number result is wanted.

  • Common mistake

    Reading % as percentage

    What it looks like

    Writing 50 % 8 and expecting a percentage

    Why it fails

    The percent sign in Java gives the remainder after division. 50 % 8 is 2.

    Do this instead

    Read % as the leftover after dividing one number by another.

  • Common mistake

    Assuming the + in a mixed expression always adds numbers

    What it looks like

    Expecting 1 + 2 + "x" to produce 3x and then 3x + 3 + 4 to produce 10

    Why it fails

    Once a String is involved, + concatenates. Each step depends on the operands present at that step.

    Do this instead

    Trace the expression from left to right and check at each + whether one side is a String.

Practice Tracing Both Ideas

MEDIUM

Trace the expression 50 % 8 + 7 / 2 + 7 / 2.0 + "!" in Java. For each sub-expression, state its operand types, whether it is integer division, remainder, or real division, and the value it contributes. Then explain whether any volatile keyword would change the result of these arithmetic steps, and why.

Hints
  • 50 % 8 is a remainder of two int operands.
  • 7 / 2 uses two int operands, so the fractional part is discarded.
  • 7 / 2.0 has a real operand, so it produces a real result.
  • The volatile keyword affects visibility of shared variables; it does not change how these literal expressions are evaluated.

Key Takeaways

  • A thread may cache a shared variable, so it can keep seeing an old value after another thread has written a new one; this is a visibility problem.
  • Declaring a variable volatile makes its changes immediately visible to all threads, but volatile does not stop a multi-step update such as count++ from being interrupted and losing updates.
  • Java's arithmetic results depend on operator and operand types: integer division discards the fractional part, while a real operand gives real-number division, and mismatched operands are converted automatically.
  • The remainder operator % gives what is left after division, unary minus negates a single operand, and the plus operator concatenates text whenever one side of it is a String.