Of course you could create more advanced concurrent mutable structures as other languages have, but IORefs and STM get you a very long way when coupled with GHC's extremely light weight threading and high performance IO (which is getting even better in GHC 7.8, to be released soon).
To learn more, Simon Marlow's excellent book is available for free online [1] and in dead tree and eBook forms from O'Reilly. It's very clear, with a great balance of how to use things practically, how they work, and how to make them work their best.
[1] http://chimera.labs.oreilly.com/books/1230000000929/index.ht...
Even though the concurrent data-structures in Java's library are efficient and well built, they remain error-prone because thread-safe operations are not composable. Working with immutable data-structures in a high concurrency scenario is the best thing since sliced bread.
I do agree with your point - sane concurrency implies using the right abstractions for the job and there are cases in which you need shared memory storage. The JVM is in fact great because it does 1:1 multithreading, because the GC is so good that it allows you to be a little wasteful and because it provides the required concurrency primitives to build whatever abstraction on top that you desire.
For Scala + the JVM, just to enumerate - you've got actors by means of Akka and it's pretty capable as it's flexible and it allows you to manipulate/coordinate entire clusters of remote actors (and of course there's Quasar that does the lightweight threads thing and I'd love a comparison between the two btw), you've got the Future/Promise concept baked in and with a sane design [2], with C#'s "async" by means of a library that will be part of the standard [3], you've got great immutable data-structures that also have parallel extensions [4], you've got RxJava/RxScala for processing streams of events reactively [5], or Iteratee IO for safe processing of data streams [6], or shared transactional memory that I mentioned above [7] and of course things inherited from Java, like NIO which in spite of its flaws it's the sanest cross-platform abstraction over native APIs for async I/O, plus as I've said, the best cross-platform concurrency primitives you can get (e.g. I still get boners every time I use ReentrantReadWriteLock).
[1] http://akka.io/
[2] http://docs.scala-lang.org/overviews/core/futures.html
[3] https://github.com/scala/async
[4] http://docs.scala-lang.org/overviews/parallel-collections/ov...
[5] https://github.com/Netflix/RxJava
[6] http://www.playframework.com/documentation/2.2.x/Iteratees
Thread-safe – yes (and Clojure does this beautifully; in fact any Clojure DS is thread-safe); concurrent – not really, because you can only have one writer at a time, which simply doesn't scale for large data structures.
> I'd love a comparison between the two btw
Quasar has true lightweight threads (M:N multithreading as in Go or Erlang), and an (optional) actor system built on top. Quasar actors can and should block as much as you want, have selective receive (just like Erlang), and are especially easy to use. Also, Quasar has Java and Clojure APIs that feel natural and idiomatic in both languages.
No STM of course, but RWArc allows for multiple writers, or you can just uses Arc if immutable sharing is acceptable, or just message passing via Chan (in the standard library) if sharing data concurrently is not required. Lots of options.
RWArc allows multiple writers, but each blocks until the "active" writer gives up its lock. But I suppose you mean that you can't have concurrent writes, so I understand this might not be suitable for the use-cases you were talking about.
[1]: http://www.youtube.com/watch?v=sq0MX3fHkro
EDIT: The Doug Lea talk linked above is probably the best overview of what doing low-level concurrency on modern hardware entails. In particular, it demonstrates interactions among abstractions layers and dispels myths that claim that having less software layers is always better (hardware already has so many layers as it is, that some software layers can actually help, if only to hide the hardware's complexity).
EDIT: Indeed it was a great talk, very informative! I came to the opposite conclusion about layers of the system, though I'd say that it isn't the number of layers that's the problem, but the nature of them.
From the talk, having the GC insert safepoints where threads can be stopped for a collection, and having the JIT inserting counters (resulting in memory contention) are two things in Java that caused for high-performance concurrency. Running on top of a VM causes similar issues. Though, clearly there are many other layers and unanticipated layer interactions, just laying in wait to mess you up regardless.
Those things are necessary for the class of programs Java SE is meant to support (long running, high-performance server-side applications); they're not ideal for device drivers, command line programs or fast-loading GUI apps, which Rust is designed to handle. Everything is a tradeoff.
EDIT: Oh, I see what you mean now; sorry, I misinterpreted. Carry on.
I wonder if nowadays people can generally use it without never having to think about locks?
Also, B-trees are a mutable data structure. "Immutable" B-trees aren't B-trees. Clojure's own persistent DS tries are quite efficient, but not nearly as scalable as a concurrent B-Tree, which easily supports lots of concurrent writes. AFAIK, there are no persistent data-structures that efficiently scale writes.
Good concurrent data structures that allow multiple concurrent writers are necessary. Java has some good basic ones like ConcurrentHashMap, ConcurrentSkipListMap, and ConcurrentLinkedQueue. Clojure takes that a step further with transactional refs. For more interesting kinds of data it's better to have a good in-memory transactional database.
So for truly "sane concurrency" you need both lightweight threads with CSP and/or actors, plus a transactional, concurrent (preferably in-memory) data store[2].
[1]: https://github.com/puniverse/quasar
[2]: http://blog.paralleluniverse.co/2013/10/16/spaceships2/