Java Lockless Programming

   In Java, explicit locks such as `synchronized` and the `Lock` interface can be used to control access to and modification of critical section resources by multiple threads, ensuring thread safety. However, Java also provides lockless programming methods to achieve the same goal while maintaining thread safety. Lockless programming in Java is primarily implemented using `volatile` and CAS operations.

        Below is an example of lockless programming, which implements the function of a lock through the CAS operation of AtomicBoolean. When implementing CAS lockless programming using atomic classes such as AtomicBoolean, there is no need to manually add the volatile modifier to the AtomicBoolean variable—because AtomicBoolean internally guarantees the visibility of the core state through volatile, and the CAS operation itself is atomic. Manually adding volatile is redundant and has no effect.

// The enum class is a static constant class that inherits from java.lang.Enum. It cannot be instantiated, that is, you cannot use new EnumLock(); 
public enum EnumLock {
// Static constant instance
INSTANCE;
// No need to modify Atomic* classes with volatile, because it has already been implemented internally through volatile
private final AtomicBoolean lockStatus = new AtomicBoolean(false);

public void lock2() {
while (!lockStatus.compareAndSet(false, true)) {
Thread.yield();
}
}

public void unlock2() {
lockStatus.set(false);
}

// Test...
private static int count = 0;

public static void main(String[] args) {
EnumLock enumLock = EnumLock.INSTANCE;
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
enumLock.lock2();
for (int j = 0; j < 100; j++) {
count++;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
enumLock.unlock2();
}
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("count => " + count);
}
}

        Simply using `volatile` to modify ordinary `boolean` or `int` values ​​will not achieve the purpose of locking. This is because `volatile` only guarantees the visibility of main memory variables in a multi-threaded environment and prohibits instruction reordering, but it cannot guarantee the atomicity of the “read-modify-write” operation. It needs to be combined with CAS atomic operations (such as  AtomicBoolean classes) to fully realize lockless programming.

        The key to lockless programming is “visibility + atomic operation + spin”. Using synchronized and Lock guarantees atomicity by blocking the program, which has context switching overhead. Lockless programming (volatile + CAS) guarantees atomicity by spin retry, which has no blocking overhead and is suitable for short-term, low-thread contention scenarios. In addition, spin retry can lead to frequent retries in high-conflict scenarios, causing CPU utilization to spike. In this case, locking is more appropriate.

        The disadvantages of lockless threads are: CAS may have ABA problems, which can be solved by using AtomicStampedReference (version number); it only supports atomicity of a single variable/operation, making it more suitable for atomic modifications of a single variable, while complex business logic is better handled by locking or transactions.