Atomic Operations in Java
Threads may cache shared data, so one thread can keep seeing an old value after another thread has changed it.
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.
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.
- volatile on
stopRequestedasks Java to make each write visible to every thread that reads the flag requestStop()may be called by one thread whilerunLoop()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.
- 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
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.
| Property | volatile | Synchronization |
|---|---|---|
| Main purpose | Makes changes visible to all threads | Coordinates threads that touch shared data |
| Prevents read-modify-write races such as count++ | No, per the source | Yes, as the usual coordination tool |
| Relative cost | More costly than non-volatile access, cheaper than synchronization | Fairly expensive computationally; excessive use should be avoided |
| Usable outside synchronized code for visibility | Yes, per the source | Not 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.
3
3.5
1- 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?
Reveal 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.
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.
- 1Identify the operand types
37.4 is a real number and 10 is an integer.
- 2Convert the mismatched operand
Because the operand types differ, Java converts the integer 10 to the real number 10.0.
- 3Evaluate with matching types
The addition is carried out on 37.4 and 10.0, both real numbers.
- 4Read the result type
Since the operation used real-number operands, the result is a real number.
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.
- 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?
Reveal 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".
Tracing the plus operator step by step
Trace how Java evaluates 1 + 2 + "x" + 3 + 4.
- 11 + 2
Both operands are numbers, so + performs addition and gives 3.
- 23 + "x"
One operand is a String, so + concatenates. The 3 is converted to text, giving "3x".
- 3"3x" + 3
The left side is a String, so the 3 is converted to text and appended, giving "3x3".
- 4"3x3" + 4
Again one side is a String, so the 4 is appended, giving "3x34".
The expression produces the String "3x34".
Mistakes Beginners Make
Common mistake Assuming volatile makes every operation on a shared variable safe
What it looks likeA volatile int counter incremented by count++ from several threads
Why it failsVolatile 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 insteadTreat 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 likeSystem.out.println(7 / 2) prints 3Why it failsTwo integer operands produce an integer result, so the fractional part is discarded.
Do this insteadUse a real operand, as in 7 / 2.0, if a real-number result is wanted.
Common mistake Reading % as percentage
What it looks likeWriting 50 % 8 and expecting a percentage
Why it failsThe percent sign in Java gives the remainder after division. 50 % 8 is 2.
Do this insteadRead % as the leftover after dividing one number by another.
Common mistake Assuming the + in a mixed expression always adds numbers
What it looks likeExpecting 1 + 2 + "x" to produce 3x and then 3x + 3 + 4 to produce 10
Why it failsOnce a String is involved, + concatenates. Each step depends on the operands present at that step.
Do this insteadTrace the expression from left to right and check at each + whether one side is a String.
Practice Tracing Both Ideas
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.