Java Concurrency, A(nother) Peak Under the Hood
Speakers: David Buck (Oracle) – @DavidBuckJP
For more blog posts, see The Oracle Code One table of contents
Multi threaded code use cases
- GUIs
- Libraries
- Batch Processing
- Worst case: writing multi-threaded code but don’t know you are
Problems
- Race conditions
- Heisenbugs/observer problem – occur in Prod (or locally), but disappear in debugger
Tools
- Java memory model
- synchronized keyword
- java.util.concurrent
Memory model
- What guarantees do you have that writes in one thread are available to another thread?
- Local variables safe
- Static variables/instance variables can introduce concurrency problems
- JIT compiler can change order things are done (vs the order you code them in)
- If optimizing compiler sees a = 1 and a=a+1, compiler will just initialize a to 2.
- Out of order execution – hardware can execute out of order to improve performance
- Clartify what multithreaded behavior developers may depend on. Limits what types of optimizations the runtime can do
- Spec: https://docs.oracle.com/javase/specs/jls/se13/html/jls-17.html
Things Java Protects us from
- Testing on Raspberry Pi because ARM so can see problems that don’t occur on Intel chips
- memory barrier/fence
- prevents reordering before before/after fence. How do to this varies by processor./tool chain. Need to set two – one on read and one on write to guarantee behavior for testing.
- Types: store-store, store-load, load-store, load-load
Relativity
- As in physics, no single “correct” timelines
- Observed order of events depends on frame of reference
- Each core can have slightly different view of memory
- This means core files aren’t 100% accurate
Changes to memory model
- Originally only dealt with synchronized – mutual exclusion and happened-before. Volatile was defined but just about visibility
- This was a problem because volatile didn’t enforce happened-before and final values could change.
- Java 5 adopted Doug Lea’s open source APIs (JSR-166)
- JSR-133 – ensured volatile does establish happens-before and final values will never changed
- Volatile != atomic
- id++ still not thread safe on a volatile field. Can use synchronized to fix
- In original memory model, 64-bit numbers (longs and doubles) not updated atomically
Practical advice
- Use highest level construct available. java.util.concurrency > synchronized/wait/notify. Last choice is volatile/final
- Don’t roll your own. Even Doug Lea didn’t get it perfect
- Java 8 – Lambdas/streams – high level
- Java 9 – Var handles – lower level than volatile/final. Alllows value to be volatile only when we want it to be. Explicit fence/acquire/release. This was introduced for use cases sun.misc.Unsafe used to deal with.
My take
This was really interesting. It was like a peek behind the curtain! My only complain is that dark red text on a black background is hard to read. Luckily there wasn’t much of it and it was obvious what was being “emphasized” in red. Also, I liked the demos!