> In Java, for example, the concurrency & threading primitives were so low level it was almost impossible for anyone to use them and get it right.
I disagree with this. As long as you had an understanding of critical sections and notify & wait, typical use cases were reasonably straightforward. The issues were largely when you ventured outside of critical sections, or when you didn’t understand the extent of your shared mutable state that needed to be protected by critical sections (which would still be a problem today, for example when you move references to mutable objects between threads — the concurrent package doesn’t really help you there).
The problem with Java pre-1.5 was that the memory model wasn’t very well-defined outside of locks, and that the guarantees that were specified weren’t actually assured by most implementations [0]. That changed with the new memory model in Java 1.5, which also enabled important parts of the new concurrency package.
[0] https://www.cs.tufts.edu/~nr/cs257/archive/bill-pugh/jmm2.pd...