CAS(Compare and Swap) and Atomic Variables in Java

One of the best additions in java 5 was Atomic operations supported in classes such as AtomicInteger, AtomicLong etc. These classes help you in minimizing the need of complex (un-necessary) multi-threading code for some basic operations such as increment or decrement a value which is shared among multiple threads. These classes internally rely on an algorithm named CAS (compare and swap).

1. Optimistic and Pessimistic Locking

Traditional locking mechanisms, e.g. using synchronized keyword in java, is said to be pessimistic technique of locking or multi-threading. It asks you to first guarantee that no other thread will interfere in between certain operation (i.e. lock the object), and then only allow you access to any instance/method.

It’s much like saying “please close the door first; otherwise some other crook will come in and rearrange your stuff”.

Though above approach is safe and it does work, but it put a significant penalty on your application in terms of performance. Reason is simple that waiting threads can not do anything unless they also get a chance and perform the guarded operation.
There exist one more approach which is more efficient in performance, and it optimistic in nature. In this approach, you proceed with an update, being hopeful that you can complete it without interference. This approach relies on collision detection to determine if there has been interference from other parties during the update, in which case the operation fails and can be retried (or not).

Compare and Swap is a good example of such optimistic approach, which we are going to discuss next.

2 Compare and Swap Algorithm

This algorithm compares the contents of a memory location to a given value and, only if they are the same, modifies the contents of that memory location to a given new value. This is done as a single atomic operation. The atomicity guarantees that the new value is calculated based on up-to-date information; if the value had been updated by another thread in the meantime, the write would fail.The result of the operation must indicate whether it performed the substitution; this can be done either with a simple Boolean response (this variant is often called compare-and-set), or by returning the value read from the memory location (not the value written to it).
There are 3 parameters for a CAS operation:

  • A memory location V where value has to be replaced
  • Old value A which was read by thread last time
  • New value B which should be written over V
    CAS says “I think V should have the value A; if it does, put B there, otherwise don’t change it but tell me I was wrong.” CAS is an optimistic technique—it proceeds with the update in the hope of success, and can detect failure if another thread has updated the variable since it was last examined.
    In both cases, the existing value in V is returned. This combines three steps – getting the value, comparing the value and updating the value – into a single machine level operation.
3. Java Compare and Swap Example

Let’s understand the whole process with an example. Assume V is a memory location where value “10” is stored. There are multiple threads who want to increment this value and use the incremented value for other operations, a very practical scenario. Let’s break the whole CAS operation in steps:
1) Thread 1 and 2 want to increment it, they both read the value and increment it to 11.
V = 10, A = 0, B = 0
2) Now thread 1 comes first and compare V with it’s last read value:
V = 10, A = 10, B = 11

if     A = V
   V = B
 else
   operation failed
   return V

Clearly the value of V will be overwritten as 11, i.e. operation was successful.

3) Thread 2 comes and try the same operation as thread 1

V = 11, A = 10, B = 11

if     A = V
   V = B
 else
   operation failed
   return V

4) In this case, V is not equal to A, so value is not replaced and current value of V i.e. 11 is returned. Now thread 2, again retry this operation with values:

V = 11, A = 11, B = 12

And this time, condition is met and incremented value 12 is returned to thread 2.

In summary, when multiple threads attempt to update the same variable simultaneously using CAS, one wins and updates the variable’s value, and the rest lose. But the losers are not punished by suspension of thread. They are free to retry the operation or simply do nothing.

4 Atomic Variables in Java

The most commonly used atomic variable classes in Java are AtomicInteger, AtomicLong, AtomicBoolean, and AtomicReference. These classes represent an int, long, boolean and object reference respectively which can be atomically updated. The main methods exposed by these classes are:

  • get() – gets the value from the memory, so that changes made by other threads are visible; equivalent to reading a volatile variable
  • set() – writes the value to memory, so that the change is visible to other threads; equivalent to writing a volatile variable
  • lazySet() – eventually writes the value to memory, may be reordered with subsequent relevant memory operations. One use case is nullifying references, for the sake of garbage collection, which is never going to be accessed again. In this case, better performance is achieved by delaying the null volatile write
  • compareAndSet() – same as described in section 3, returns true when it succeeds, else false
  • weakCompareAndSet() – same as described in section 3, but weaker in the sense, that it does not create happens-before orderings. This means that it may not necessarily see updates made to other variables
5 Lock VS Atomic Variables
5.1 Lock

Let’s have a look at the class:

public class Counter {
    int counter; 
  
    public void increment() {
        counter++;
    }
}

In the case of a single threaded environment, this works perfectly; however, as soon as we allow more than one thread to write, we start getting inconsistent results.

This is because of the simple increment operation (counter++), which may look like an atomic operation, but in fact is a combination of three operations: obtaining the value, incrementing, and writing the updated value back.

If two threads try to get and update the value at the same time, it may result in lost updates.

One of the ways to manage access to an object is to use locks. This can be achieved by using the synchronized keyword in the increment method signature. The synchronized keyword ensures that only one thread can enter the method at one time.

public class SafeCounterWithLock {
    private volatile int counter;
  
    public synchronized void increment() {
        counter++;
    }
}

Additionally, we need to add the volatile keyword to ensure proper reference visibility among threads.

Using locks solves the problem. However, performance takes a hit.

When multiple threads attempt to acquire a lock, one of them wins, while the rest of the threads are either blocked or suspended.

The process of suspending and then resuming a thread is very expensive and affects the overall efficiency of the system.

In a small program, such as the counter, the time spent in context switching may become much more than actual code execution, thus greatly reducing overall efficiency.

5.2 Atomic Variables

A thread safe counter implemented with AtomicInteger is shown in the example below:

public class SafeCounterWithoutLock {
    private final AtomicInteger counter = new AtomicInteger(0);
     
    public int getValue() {
        return counter.get();
    }
    public void increment() {
        while(true) {
            int existingValue = getValue();
            int newValue = existingValue + 1;
            if(counter.compareAndSet(existingValue, newValue)) {
                return;
            }
        }
    }
}

参考:
《Java Compare and Swap Example – CAS Algorithm》
《An Introduction to Atomic Variables in Java》
《How CAS (Compare And Swap) in Java works》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值