The Java Memory Model gives some minimal guarantees about what happens if two threads are accessing the same variables. Brian Goetz describes this in chapter 7 of Java Concurrency in Practice. And here is a good summary of the new (1.5) Memory Model. Doug Lea describes the old memory model. If you really want to dig into it read some more formal specs.
The essence of the java memory models is: If one thread modifies a variable another thread may not see the change unless "some synchronization" happens (a synchronized block, a shared lock etc). But most real java VMs implement a much stricter memory model, that means if one thread modifies a variable the other thread sees it even without synchronization. And that is the problem. It is almost impossible to find those threading problems without a java VM that implements only the minimal memory model guarantees.
My favorite "theoretical threading bug" is NullProgressMonitor.cancelled should be volatile. If a java VM would implement only the minimal memory model the code would not work, but as John points out: "This is true in theory, but never happens in practice. In practice, the thread calling isCanceled may obtain a stale result for a short period of time, but the thread cache is soon synchronized. It is common practice to omit synchronization in cases where obtaining a stale value is acceptable.". This is unfortunately true. At least I could not construct an example where one thread would not see the changes made by another thread.
Getting threading right is extremely hard. Deadlocks often occur only if you have a bad day. You can get away with obviously wrong code just because Java VMs are so gracious.
I wonder if there is java VM that implements only the minimal memory model? It would be cool if it would be possible to force the VM to behave like the worst possible memory model. For example: it would not make changes visible to other threads unless the data is synchronized. This would be extremely helpful for testing and debugging purposes. I wonder how well eclipse would behave on such a "minimal memory model VM"....