Keywords: Atomicity | Concurrent Programming | Java Memory Model
Abstract: This article provides an in-depth exploration of atomicity in programming, analyzing Java language specifications for atomic operation guarantees and explaining the non-atomic characteristics of long and double types. Through concrete code examples, it demonstrates implementation approaches using volatile keyword, synchronized methods, and AtomicLong class, combining visibility and ordering principles in multithreading environments to deliver comprehensive atomicity solutions. The discussion extends to the importance of atomic operations in concurrent programming and best practices.
Fundamental Concepts of Atomicity
In the realm of concurrent programming, atomicity stands as a critical concept. An atomic operation refers to an indivisible unit of work that either completes entirely or not at all, with no intermediate states observable. From an observer's perspective, atomic operations are perceived as either not yet started or fully completed, never caught in the midst of execution.
Atomicity Guarantees in Java Language Specification
According to the Java Language Specification (JLS 17.4.7), reading or writing variables is atomic by default, with the exception of long and double types. This exception exists because in 32-bit JVMs, 64-bit values for long and double require two separate operations for reading and writing, each handling 32 bits.
Potential Issues with Non-Atomic Operations
Consider the following code example:
long foo = 65465498L;
This assignment operation actually consists of two separate 32-bit write operations at the底层 level. In multithreading environments, if one thread is executing this assignment while another thread simultaneously reads the value of foo, it might observe an intermediate state—an incomplete value where only the first 32 bits or the last 32 bits have been written.
Methods for Achieving Atomicity
Using the volatile Keyword
Declaring a variable as volatile ensures atomic read and write operations:
private volatile long foo;
The volatile keyword not only guarantees visibility (immediate propagation of changes across threads) but also ensures atomicity through memory barriers.
Synchronized Methods
Synchronization mechanisms can ensure atomic access to variables:
public synchronized void setFoo(long value) {
this.foo = value;
}
public synchronized long getFoo() {
return this.foo;
}
This approach offers finer control but requires ensuring all variable accesses occur through synchronized methods.
Using AtomicLong Class
Java's concurrency package provides specialized atomic classes:
private AtomicLong foo = new AtomicLong();
AtomicLong employs CAS (Compare-And-Swap) operations to achieve lock-free atomic updates, making it preferable in high-performance scenarios.
Essential Characteristics of Atomicity
From a broader perspective, atomic operations exhibit "one at a time" characteristics. In other programming languages like Swift, non-atomic dictionary operations can lead to similar issues—one thread might read from a dictionary while another modifies it, resulting in inconsistent observations.
Practical Implementation Recommendations
When selecting atomicity implementation strategies, consider specific application contexts:
- For simple visibility requirements,
volatileoffers a lightweight solution - Synchronized methods provide better control for complex synchronization logic
- Atomic classes typically deliver superior performance in high-concurrency scenarios
Understanding the essence of atomicity contributes to writing safer, more reliable concurrent programs.