Skip to content

clojure.core/compare-and-set!

View this page on ClojureDocs

Type: function Added: Clojure 1.0 Examples: 2 Runnable: 1
([atom oldval newval])

Atomically sets the value of atom to newval if and only if the current value of the atom is identical to oldval. Returns true if set happened, else false

Examples

by yasuto on
by reborg on . May have evaluation errors.

Note by chbrown

compare-and-set! actually runs an equality comparison, not an identity comparison. The documentation should read:

Atomically sets the value of atom to newval if and only if the current value of the atom is identical equal to oldval.

(def my-sym (atom 'a))
(identical? @my-sym 'a)
;;=> false
(= @my-sym 'a)
;;=> true
(compare-and-set! my-sym 'a 'z)
;;=> true
@my-sym
;;=> z

Note by favila

The note that compare-and-set! uses equality comparison is wrong, compare-and-set! really does use identity comparison (Java ==). Internally, Clojure uses the AtomicReference.compareAndSet(old, new) method.

The reason his example works is due to interning of the a symbol: in his example, each a is the same (identical) object.

But as you can see from the example below, even numeric autoboxing can lead to surprising results:

(def a (atom 0))
;=> #'user/a
(compare-and-set! a 0 100)
;=> true
;(compare-and-set! a 100 200)
;=> true
;; Fails?! (on Oracle JVM 8 with default settings)
(compare-and-set! a 200 300)
;=> false
@a
;=> 200 ; WAT?

Clojure almost always uses boxed numbers (via Java autoboxing) unless you take special steps to avoid it. compare-and-set! only accepts Objects, so numbers are autoboxed to Longs.

Java JVMs will usually intern small integers; by default Oracle/OpenJDK will intern -127 to 128 (the byte range) so that all such boxed numbers are identical. This can be altered with the -XX:AutoBoxCacheMax= command line flag. This may vary by JVM implementation, too.

(In fact, on ClojureCLR, compare-and-set! of longs always fails because the CLR does not intern small numbers, see this bug report.)

So in the example above, compare-and-set! on 0 and 100 work fine due to this auto-interning, but the compare with 200 fails because: (identical? 200 200) is false due to autoboxing: two distinct invisible Long objects are created for each "200" value.

You don't have to worry about this with swap! because the "old" value it compares against for the compare-and-set operation is always from the atom itself, so identity comparison works as long as no one else put a different object in the atom in the meantime. However, it is easy to imagine a pathological case with a highly-contented atom where everyone keeps putting the same "equal" value into it over and over, and yet swappers have to retry over and over.

See also


Content from the matching ClojureDocs page, with authors credited on each contribution.