OK, the title of this post is intentional hyperbole. Obviously there is correct Java code, even a lot, but I have a feeling it might be a statistically negligible fraction of the total. How can I make such an extreme claim?
Well, it all comes back to the Java Memory Model, and how it conflicts with most people's intuitive idea of what happens "in the computer" when their code is running. Now, lest I be accused of unfairly criticizing Java, let me make it clear that I understand Java was the first widespread language that even tried to provide a consistent, accessible and cross platform environment for writing multithreaded code. And that's hard. The fact that the first attempt got some important details wrong should surprise nobody. And those problems were fixed by JSR-133, and have been widely deployed since Java 5.
But even those of us who are working in recent Java environments, with their more forgiving and predictable memory model, still get a lot wrong, and we've been blissfully unaware of it. How can that be? As Brian Goetz, author of the indispensable Java Concurrency in Practice (about which more later) puts it so well:
...because the most commonly used processors (Intel and Sparc) offer stronger memory models than is required by the JMM, many developers frequently use synchronization and volatile incorrectly, but have been insulated from failure by the stronger memory guarantees offered by the processor architecture they happen to be deploying on.
This quotation is from the abstract of a talk he's giving later this month. I wish my schedule allowed me to get there. Because we Java developers are being abruptly roused from our blissful, erroneous ways by the fact that more and more computers are shipping with multiple processors, each with multiple cores, and the memory behavior we're used to relying on is starting to disappear.
And it's as if we were set up for failure. Many of the ways we learned Java, be it through example programs, classroom instruction, or books, got these things wrong. So we have deeply ingrained habits of thought which are simply false and dangerous, and cause us to think about programs in a harmful way... a way which leads to subtle bugs in deployment, which are extremely hard to track down and fix.
But surely this only matters for multithreaded programs? And aren't those rare, written by specialists? Well, not so much. Consider any program that uses the Swing graphical interface toolkit. Or, even more common, a server application living in a Servlet container. In both cases you are forced into a multithreaded environment.
I think most of us have a good handle on the notions of race conditions and deadlock, especially as they relate to multiple variables, and how to cope through careful use of synchronization. That is hard enough to learn, but there have long been good examples, and enough environmental encouragement, to put us on the right track towards understanding and working with threaded code at that level.
Even so, the persistence of broken approaches like double-checked locking has long been evidence of problems lurking around the edges of understanding. And where things really fall apart worse than we realized is in issues of visibility. Even values that are pretty much stable and unchanging may never be seen by other threads if you don't properly use synchronization or
volatile to propagate them. Our mental model of one, shared pool of main memory is just an oversimplification that leads to incorrect predictions about what code will do and what is safe.
This was driven home to me recently when we started seeing problems with one of our products at a customer site on a multiprocessor machine. It was behaving in ways we could not reproduce ourselves, which had never shown up in our testing, but which could be resolved by disabling one of the processors. Shortly after that, one of my colleagues gave a presentation at a local Java user group based on some things he had picked up in Java Concurrency in Practice, which reminded me that I had really been meaning to read that book, so maybe now was the time to get around to it...
So I did. And it really opened my eyes in an important way. It crystallized issues I had heard about here and there at Java One and on the web, but had never built into a coherent picture. It has forced me to learn more about what Java code really means, and how to write it safely. Our team has been jointly reading it and discussing chapters at lunch for a few weeks, and it will enable us to fix our code so it runs properly on multiple processors, which used to be expensive and exotic, but are rapidly becoming the norm. So it was none too early for us--we wish we had known this stuff in depth long ago. It will take a while to internalize some of it, and to regain confidence in our ability to write working code, but at least we are now on the right track.
Anyone writing in Java owes it to themselves, and to their users, to read this book. A summary of the issues I've alluded to in this posting can be found in a pair of articles Brian Goetz wrote for IBM's developerWorks, but they are no substitute for all the analysis, advice, and examples which make up Java Concurrency in Practice.
Still... even once we get good at watching for such issues, there are parts of this problem which remain irreducibly hard. This is prompting me to think about taking a closer look at Scala, as a newer language that can run in the JVM along with our other code, and tackle the thornier parallel portions from the more stable and trustworthy platform of functional programming, with its mostly immutable objects. Scala's share-nothing, actor-based message passing parallelism, an approach that has been so successful in Erlang outside of the JVM, is showing promise. But that is another can of worms for another day.