Atomics

a[0]++ on shared memory is read, add, write: two threads can read the same old value and lose an increment. The static Atomics methods on integer views of a SharedArrayBuffer never interleave: load(), store(), exchange(), compareExchange(), add(), sub(), and(), or() and xor(), plus wait() (workers only), waitAsync() (ES2024, Baseline 2025), notify(), the spin-loop hint pause() and isLockFree().

Four workers race on ++ while Atomics.add() stays exactJavaScript
import { Worker } from 'node:worker_threads';
const shared = new Int32Array(new SharedArrayBuffer(12));  // [0] ++, [1] add, [2] done
const code = `const { workerData: a } = require('node:worker_threads');
  for (let i = 0; i < 1e6; i++) { a[0]++; Atomics.add(a, 1, 1); }
  Atomics.add(a, 2, 1); Atomics.notify(a, 2);`;
for (let i = 0; i < 4; i++) new Worker(code, { eval: true, workerData: shared });
let done;
while ((done = Atomics.load(shared, 2)) < 4) {
  await Atomics.waitAsync(shared, 2, done).value;           // never blocks the main thread
}
console.log('++:', shared[0], 'Atomics.add:', shared[1]);
Output
++: 1821014 Atomics.add: 4000000

The ++ total changes every run (the next printed 1801222). waitAsync() returns 'not-equal' at once if the slot already moved, so the loop re-reads it. Atomics protects single operations only; check-then-act needs compareExchange() or a lock.