Mostly the same as ordinary concurrency, but Java gives you some unique language constructs, very mature concurrent data-structures, and a well-defined memory model that's quasi-independent of the hardware it's implemented on.1
In the development I do, 90% of the concurrency bugs I see are either failures to apply double checked locking correctly, or completely accidental sharing of a non-thread safe object. An even larger percent of the tasks that need concurrency can be handled by either safely publishing immutable objects and/or using a few core classes (ConcurrentHashMap, concurrent queues, thread pools). Stick with these concepts and apply a few rules diligently, and programming with threads is not so bad.
However, there is an entire world of concurrency related issues that you'll need to understand if you're interested in building the abstractions you use in day to day programming, or you're just curious. Things get much more difficult here, and I'm still a novice.
This is one of the best books I've read about programming, and the best book I've read about Java. It has about 30 pages on concurrency, but anyone writing Java should read the whole book.
Double checked locking is the source of the most common concurrency errors I've seen. It's also mentioned in the Bloch book, but this article gives much more of an explanation.
Note that this article was written when Java 5 was not yet released, so it delays the good news that you can properly use double checked locking to the end.
A book length treatment of Java concurrency. The focus is on the higher level abstractions that Java provides, which is good. A huge proportion of cases can be effectively handled by just using what the standard library gives you. That said, if I recall correctly, this book also gives you an explanation of the primitives.
The book covers Java 1.6, which includes most of the modern Java concurrency features, but not the Fork-Join framework, streams based concurrency, and CompletableFuture. Hopefully there will be an update soon.
Here Be Dragons
The semantics of multithreaded programs are part of the JLS, chapter 17. The most difficult part of that specification is the Java Memory Model, which determines what values can be seen by reads in separate threads. The memory model essentially dates to Java 5--the version that shipped with Java 1 was considered fatally flawed.
Mailing list devoted to high performance, with a lot of people working in Java, and frequent discussions of concurrency.
Actor model frameowrk for the JVM.
A test harness for concurrent programs.
If the Java memory model guarantees something, it will be true on all types of hardware, but effects it doesn't guarantee may be inconsistent between platforms. My favorite example is that reads/writes for longs are not atomic on 32 bit architectures! But this belongs in a footnote because while it sounds really scary, it's an edge case.↩