...
| Code Block |
|---|
| language | java |
|---|
| title | Proposed RecordHeader Change |
|---|
| linenumbers | true |
|---|
|
public class RecordHeader implements Header {
private ByteBuffer keyBuffer;
- private String key;
- private ByteBuffer valueBuffer;
- private byte[] value;
+ private volatile String key;
+ private volatile ByteBuffer valueBuffer;
+ private volatile byte[] value;
...
public String key() {
if (key == null) {
+ synchronized (this) {
+ if (key == null) {
key = Utils.utf8(keyBuffer, keyBuffer.remaining());
keyBuffer = null;
+ }
+ }
}
return key;
}
public byte[] value() {
if (value == null && valueBuffer != null) {
+ synchronized (this) {
+ if (value == null && valueBuffer != null) {
value = Utils.toArray(valueBuffer);
valueBuffer = null;
+ }
+ }
}
return value;
}
} |
...
The benchmark was executed on an Apple M4 Max system with 48 GB RAM.
The thread-safe version adds a little overhead during the first initialization (key() 0.457 → 0.770 ns/op, value() 0.451 → 0.761 ns/op).
After the initial lazy initialization, subsequent accesses do not incur any locking or additional cost, so the steady-state performance remains essentially identical to the non-thread-safe versionFull-Method Synchronization means that the entire key()/ value() method is synchronized.
| Code Block |
|---|
| language | java |
|---|
| title | RecordHeaderBenchmarkRecord Header Single Thread Benchmark |
|---|
| linenumbers | true |
|---|
|
@State(Scope.Benchmark)
@Fork(value = 1)
@Warmup(iterations = 5)
@Measurement(iterations = 15)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class RecordHeaderBenchmarkRecordHeaderSingleThreadBenchmark {
private RecordHeader header;
@Setup(Level.Iteration)
public void setup() {
byte[] valueBytes = new byte[1000];
ByteBuffer keyBuffer = ByteBuffer.wrap("key".getBytes());
ByteBuffer valueBuffer = ByteBuffer.wrap(valueBytes);
header = new RecordHeader(keyBuffer, valueBuffer);
}
@Benchmark
public String benchmarkKey() {
return header.key();
}
@Benchmark
public byte[] benchmarkValue() {
return header.value();
}
} |
| Code Block |
|---|
| language | bash |
|---|
| title | Current Implementation single thread result |
|---|
| linenumbers | true |
|---|
|
Benchmark Mode Cnt Score Error Units
RecordHeaderSingleThreadBenchmark.benchmarkKey avgt 15 0.457 ± 0.023 ns/op
RecordHeaderSingleThreadBenchmark.benchmarkValue avgt 15 0.451 ± 0.020 ns/op |
| Code Block |
|---|
| language | bash |
|---|
| title | Double-Check Locking single thread result |
|---|
| linenumbers | true |
|---|
|
Benchmark Mode Cnt Score Error Units
RecordHeaderSingleThreadBenchmark.benchmarkKey avgt 15 0.774 ± 0.010 ns/op
RecordHeaderSingleThreadBenchmark.benchmarkValue avgt 15 0.773 ± 0.008 ns/op |
Full-Method Synchronization means that the entire key()/ value() method is synchronized.
This benchmark use code in the above section with 8 threads to compare performance.
| Code Block |
|---|
| language | java |
|---|
| title | RecordHeaderBenchmark |
|---|
| linenumbers | true |
|---|
|
public class RecordHeaderBenchmark {
...
@Benchmark
+ @Threads(8)
public String benchmarkKey() {
return header.key();
}
@Benchmark
+ @Threads(8)
public byte[] benchmarkValue() {
return header.value();
}
} |
The benchmark was executed on an Apple M4 Max system with 48 GB RAM.
Double-Checked Locking is significantly faster (286×) than full-method synchronization.
...