cow: add committed javap evidence for the internal lock field, renumber the preserved race-condition output to make room for it
This commit is contained in:
+6
-4
@@ -20,9 +20,10 @@ mvn compile
|
|||||||
java -cp target/classes com.ankurm.cow.IteratorSnapshotDemo
|
java -cp target/classes com.ankurm.cow.IteratorSnapshotDemo
|
||||||
```
|
```
|
||||||
|
|
||||||
`scripts/run-all.sh` regenerates `output/01-05` (not `06`, see below). `scripts/run.sh <ClassName>`
|
`scripts/run-all.sh` regenerates `output/01-06` (not `07`, see below). `scripts/run.sh <ClassName>`
|
||||||
runs one demo ad hoc. The JMH benchmarks also run as an executable jar: `mvn package -DskipTests`
|
runs one demo ad hoc. The JMH benchmarks also run as an executable jar: `mvn package -DskipTests`
|
||||||
then `java -jar target/benchmarks.jar <BenchmarkName>`.
|
then `java -jar target/benchmarks.jar <BenchmarkName>`. The lock-field check is a one-line `javap`
|
||||||
|
command, also reproducible directly: `javap -p java.util.concurrent.CopyOnWriteArrayList | grep -i lock`.
|
||||||
|
|
||||||
## What's in here
|
## What's in here
|
||||||
|
|
||||||
@@ -34,12 +35,13 @@ then `java -jar target/benchmarks.jar <BenchmarkName>`.
|
|||||||
| `WriteThroughputBenchmark.java` | JMH: cost of an `addLast`/`removeFirst` pair at sizes 10 and 1000, with 1 writer and with 4 concurrent writers. |
|
| `WriteThroughputBenchmark.java` | JMH: cost of an `addLast`/`removeFirst` pair at sizes 10 and 1000, with 1 writer and with 4 concurrent writers. |
|
||||||
| `CowClaimsTest.java` | Pins all of the above as assertions, 7/7 passing. |
|
| `CowClaimsTest.java` | Pins all of the above as assertions, 7/7 passing. |
|
||||||
| `output/01-05` | Captured runs of the two demos, the test suite, and both JMH benchmarks. |
|
| `output/01-05` | Captured runs of the two demos, the test suite, and both JMH benchmarks. |
|
||||||
| `output/06-write-throughput-race.txt` | See below - not something `run-all.sh` regenerates. |
|
| `output/06-javap-lock-field.txt` | `javap -p` output for the internal lock field - see below. |
|
||||||
|
| `output/07-write-throughput-race.txt` | See below - not something `run-all.sh` regenerates. |
|
||||||
|
|
||||||
## Notes worth knowing before reading the post
|
## Notes worth knowing before reading the post
|
||||||
|
|
||||||
- **The internal lock is a plain `Object` monitor, not a `ReentrantLock`.** `javap -p java.util.concurrent.CopyOnWriteArrayList` on JDK 25 shows `final transient java.lang.Object lock;` - a lot of writing (including earlier drafts of this very post) describes it as "a global reentrant lock." That description matches older JDKs; verify against the jar you're actually running before repeating it.
|
- **The internal lock is a plain `Object` monitor, not a `ReentrantLock`.** `javap -p java.util.concurrent.CopyOnWriteArrayList` on JDK 25 shows `final transient java.lang.Object lock;` - a lot of writing (including earlier drafts of this very post) describes it as "a global reentrant lock." That description matches older JDKs; verify against the jar you're actually running before repeating it.
|
||||||
- **`output/06-write-throughput-race.txt` is a deliberately preserved bug, not a regenerable artifact.** The first version of `WriteThroughputBenchmark` used `list.add(-1); list.remove(list.size() - 1);` - two separate calls, each individually thread-safe, but not atomic as a pair. Under 4 concurrent writer threads this raced and threw a real `ArrayIndexOutOfBoundsException` on `CopyOnWriteArrayList` and a real `IndexOutOfBoundsException` on `Collections.synchronizedList` - both captured verbatim in that file. The fix, `addLast()`/`removeFirst()` (JEP 431 default-turned-overridden methods, each a single call with no externally-fetched index), is what the committed benchmark actually runs. Left in deliberately: it's a more convincing demonstration of "thread-safe per-call does not mean safe as a sequence of calls" than a paragraph describing the same thing would be.
|
- **`output/07-write-throughput-race.txt` is a deliberately preserved bug, not a regenerable artifact.** The first version of `WriteThroughputBenchmark` used `list.add(-1); list.remove(list.size() - 1);` - two separate calls, each individually thread-safe, but not atomic as a pair. Under 4 concurrent writer threads this raced and threw a real `ArrayIndexOutOfBoundsException` on `CopyOnWriteArrayList` and a real `IndexOutOfBoundsException` on `Collections.synchronizedList` - both captured verbatim in that file. The fix, `addLast()`/`removeFirst()` (JEP 431 default-turned-overridden methods, each a single call with no externally-fetched index), is what the committed benchmark actually runs. Left in deliberately: it's a more convincing demonstration of "thread-safe per-call does not mean safe as a sequence of calls" than a paragraph describing the same thing would be.
|
||||||
- **Read throughput numbers are dramatic, not subtle.** See the post for the real ratio - this is the headline number and it's larger than most people expect even having read "CopyOnWriteArrayList is for read-heavy workloads" a dozen times.
|
- **Read throughput numbers are dramatic, not subtle.** See the post for the real ratio - this is the headline number and it's larger than most people expect even having read "CopyOnWriteArrayList is for read-heavy workloads" a dozen times.
|
||||||
- **This sandbox is shared and multi-tenant.** Several rows in `output/05` carry an error margin wider than the mean. Read the shape (which variant is bigger, by roughly how much) rather than quoting any single ops/ms figure to three significant digits.
|
- **This sandbox is shared and multi-tenant.** Several rows in `output/05` carry an error margin wider than the mean. Read the shape (which variant is bigger, by roughly how much) rather than quoting any single ops/ms figure to three significant digits.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
$ javap -p java.util.concurrent.CopyOnWriteArrayList | grep -i lock
|
||||||
|
final transient java.lang.Object lock;
|
||||||
|
private void resetLock();
|
||||||
@@ -16,6 +16,10 @@ mvn -q -B package -DskipTests
|
|||||||
java -jar target/benchmarks.jar ReadThroughputBenchmark -rf text -rff output/04-read-throughput-jmh.txt
|
java -jar target/benchmarks.jar ReadThroughputBenchmark -rf text -rff output/04-read-throughput-jmh.txt
|
||||||
java -jar target/benchmarks.jar WriteThroughputBenchmark -rf text -rff output/05-write-throughput-jmh.txt
|
java -jar target/benchmarks.jar WriteThroughputBenchmark -rf text -rff output/05-write-throughput-jmh.txt
|
||||||
|
|
||||||
echo "Regenerated output/01-05. output/06-write-throughput-race.txt is a one-off captured failure"
|
{ echo '$ javap -p java.util.concurrent.CopyOnWriteArrayList | grep -i lock'; \
|
||||||
|
javap -p java.util.concurrent.CopyOnWriteArrayList 2>&1 | grep -vE 'JAVA_TOOL_OPTIONS|^WARNING' | grep -i lock; } \
|
||||||
|
> output/06-javap-lock-field.txt
|
||||||
|
|
||||||
|
echo "Regenerated output/01-06. output/07-write-throughput-race.txt is a one-off captured failure"
|
||||||
echo "from an earlier, deliberately buggy version of the write benchmark - see the README - and"
|
echo "from an earlier, deliberately buggy version of the write benchmark - see the README - and"
|
||||||
echo "is NOT regenerated by this script."
|
echo "is NOT regenerated by this script."
|
||||||
|
|||||||
Reference in New Issue
Block a user