Skip to content

Instantly share code, notes, and snippets.

@afsalthaj
Last active May 15, 2018 02:19
Show Gist options
  • Save afsalthaj/b2b9362d2c77de88b503e14ca9e28caa to your computer and use it in GitHub Desktop.
Save afsalthaj/b2b9362d2c77de88b503e14ca9e28caa to your computer and use it in GitHub Desktop.
// The code base below shows how horrible it is to use AtomicReference
// (Yes, we are talking about concurrency) in the world of mutation.
// This should be one of your answer to why you hate mutation for the sake of performance - 
// The gist is a simple example to ABA problem.
// While the problem sounds ridiculously easy, the gist show how can it happen in a real world,
// and how on earth can it lead to program error.
@ val s = new AtomicLong(0)
s: AtomicLong = 0
@ s.compareAndSet(1, 2)
res14: Boolean = false
@ // this is because the atomic long currently hold a zero and when u did a compareAndSet assuming its current state is 1, it failed
@ // In concurreny world, The CAS operation would be called again tail recursively and make sure state is fixed
@ val ss = s.get
ss: Long = 0L
@ s.compareAndSet(ss, 1)
res16: Boolean = true
@ // Now the first thread was successful in setting up its value to atomic. It rechecked the current value and set with the new value accordingly.
@ // lets get into the ABA problem now
@ // Thread 1
@ class State (var n: Int)
defined class State
@ // lets have an atomic variable
@ val baseState = new State(0)
baseState: State = ammonite.$sess.cmd17$State@1df17084
@ val atomic = new AtomicReference[State](baseState)
atomic: AtomicReference[State] = ammonite.$sess.cmd17$State@1df17084
@ // Thread1 knows the current state of the atomic as `baseState` and with that info it called compareAndSet with a newState and it worked. Let's make up a rule here. If baseState n == 0, then
// the new state should be 100. If it is above 0, then it should be 1000.
@ atomic.compareAndSet(baseState, new State(100))
res20: Boolean = true
@ // It worked.. assume that another thread T2 was waiting during this time to do compareAndSet with the previous value as `baseState` as it didn't know thread1 has already completed that
@ // Now right after Thread1 did compareAndSet, Thread3 went ahead and updated atomic with a State but not as a new State object, but an existing state object with a change in its mutable field..
@ // Thread 3
@ baseState.n = atomic.get.n + 1
@ val currentState = atomic.get
currentState: State = ammonite.$sess.cmd17$State@6d1cff5
@ atomic.compareAndSet(currentState, baseState)
res23: Boolean = true
@ // That's a success as expected. It knows its current state is `currentState` and reloaded the atomic with an old object `baseState` with a change in its mutable field as explained before
@ // Now its time for Thread T2.We expect Thread T2 to know that something has happened after it read the atomic state and before it is going to write its new value say new State(100)
@ // That is because it is calculated the new State(100) based on the current instance of baseObject at that point in time ...may be because its n was 1 at that point in time
@ // Now it will call compareAndSet with baseState which will pass and then update the atomic with a new state which was calculated with an old value of baseState
@ atomic.compareAndSet(baseState, new State(100)) // the one which Thread1 did... but many things (involving) happened after that which Thread 2 doesn't seem to know...
res24: Boolean = true
@ atomic.get.n
res25: Int = 100
@ // it was already 101.... and the program is in an error state, as it is supposed to be 1000 now. Or in other words, it should have asked for the newState and updated it accordingly.
@
// The ABA problem here is Thread2 read a state to be at A, and then Thread 1 meanwhile wrote B and Thread 3 made it back to a baseState with a mutable variable but for Thread2 it is till A, and continues the operation thinking the state is A itself
@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment