Equals On Atomic Value
ID |
java.equals_on_atomic |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Java |
Tags |
concurrency, reliability |
Description
Reports .equals(..) calls whose receiver is declared AtomicBoolean, AtomicInteger,
AtomicLong, AtomicReference or one of the Atomic*Array types. None of these classes
override Object.equals(Object), so the call compares object identity instead of the wrapped
value.
Rationale
java.util.concurrent.atomic classes are mutable holders built for lock-free updates, not value
types. They inherit equals(Object) straight from Object, so it returns true only when both
references point at the very same instance:
AtomicLong a = new AtomicLong(42);
AtomicLong b = new AtomicLong(42);
if (a.equals(b)) { // Bug: always false here -- a and b are different instances
merge(a, b);
}
The condition above is always false even though both counters hold 42, so merge never
runs. Because the code compiles and equals looks like the right method to call, the mistake
usually survives review and only shows up as a hard-to-reproduce behavioural bug.
Remediation
Compare the wrapped value with .get(), or compare identity explicitly with == when that is
really the intent:
AtomicLong a = new AtomicLong(42);
AtomicLong b = new AtomicLong(42);
if (a.get() == b.get()) { // Compares the wrapped value
merge(a, b);
}
if (a == b) { // Compares identity, and says so
reportDuplicateReference();
}