views:

1955

answers:

20

I was wondering if some of you who are experienced in concurrency programming could help me interpret a statement/philosophy properly.

I have a copy of Bruce Eckel's grand tome Thinking In Java (4th ed) which has some fairly good coverage of a number of areas of Java which are kind of difficult for beginners to get into. I really enjoyed the chapters on classes, generics, and annotations -- they cleared up a number of questions in my mind.

But then I got most of the way through Eckel's 193-page chapter on concurrency (after reading the excellent Java Concurrency in Practice) and got to this bit: (p. 1278)

Fast forward to the sixth printing of the book, and most new machines have at least two cores on them, as did the machine I was using. And I was surprised when it [one of the programs he wrote for this chapter --J.S.] broke, but that's one of the problems. It's not Java's fault; "write once, run everywhere" cannot possibly extend to threading on single vs. multicore machines. It's a fundamental problem with threading. You can actually discover some threading problems on a single-CPU machine, but there are other problems that will not appear until you try it on a multi-CPU machine, where your threads are actually running in parallel.

And most important: you can never let yourself become too confident about your programming abilities when it comes to shared-memory concurrency. I would not be surprised if, sometime in the future, someone comes up with a proof to show that shared-memory concurrency programming is only possible in theory, but not in practice. It's the position I've adopted.

WTF? Am I missing something? Does Java (+ other languages/OS calls, for that matter) not offer tools to provide sufficient guarantees of concurrency-correctness to run real-world applications? This is not just a rhetorical question, I'm wondering if there's some good documentation on how to know if you're handling something "correctly" or whether there is "fine print" that causes a concurrency guarantee to be broken.

for example: if you use a synchronized keyword on a class's method, does that really guarantee that only one thread at a time can be executing code within that method on a particular object? Or is there a gotcha if I use a multicore CPU?

I've read (but not fully understood) a number of technical papers on semaphores, concurrent linked lists, etc. and in most cases it looks like they have taken great pains to rigorously prove correctness of concurrency primitives... but now I'm not sure how to deal with this stuff. (My fallback position is to ignore this chapter of TIJ and reread Java Concurrency in Practice enough times that it makes sense to me again.)

+23  A: 

It's the difference between theory and practice. Theoretically it's possible to write code that has no problems with concurrency. However, concurrent programming can get very complex very quickly, so practically it's very difficult to create something that's perfect.

Of course, this is nothing new. Theoretically it's possible to write code that has zero bugs. But in practice it pretty much never happens.

Herms
It is just the matter of a proper definition of "bug".
Thorbjørn Ravn Andersen
+30  A: 

Don't worry, synchronized works OK. I think that Eckel is trying to say that thread-based concurrent programming is full of gotcha's waiting around every corner. Doing concurrent stuff always feels like black magic for me, although my understanding surely improved after reading JCiP.

Peter Štibraný
+1, that's basically the essence of the text. It's not that the low-level constructs don't do what they say; it's just very difficult to translate high-level ideas of thread-safe behaviour into the low-level stuff (like synchronized, use of java.util.concurrent etc.) 100% correctly.
Andrzej Doyle
+5  A: 

In theory, theory and practice are the same. In practice, they're not.

Paul Tomblin
A citation, produce you must. -- Yoda
Randolpho
I think it was Yogi Berra. If it wasn't, it might as well have been.
Michael Myers
I bask in your brilliance, oh wise Zen master.
Michael Angstadt
+1 because this draws attention to the abuses of the word "theoretically" in some of the other answers.
finnw
+1  A: 

Something from the quote that amuses me:

I would not be surprised if, sometime in the future, someone comes up with a proof to show that shared-memory concurrency programming is only possible in theory, but not in practice.

Isn't that contradictory? How can you both prove that something is possible and not possible at the same time?

I guess not really relevant to the question asked, but for some reason I felt the need to point it out.

Herms
In theory there is no difference between practice and theory, but in practice there is. - Yogi Berra, in theory.
Terry Wilcox
That makes at least four people who thought of that quote. It's still good, but now it's losing some of its luster.
Michael Myers
@Herms Exactly what I was thinking
Draemon
+20  A: 

Synchronization works fine, it's just hellishly difficult to apply correctly everywhere it's needed - and if you do it wrong or forget doing it somewhere, your code may run fine for weeks, until it's deployed on a production machine with more cores and a real-world load, promptly causing a race condition to manifest and mutilate yor data before crashing the app.

Michael Borgwardt
This is not really addressing the issue raised regarding WORA.The issue is that different *hardware* have different policies regarding individual core memory synchronizations with the machine's main memory. Different policies regarding Cache-coherence (if applicable), cache-mainline hits, etc. are the physical facts that an idealized *virtual platform* specification tries to address with a memory model spec (e.g. JMM). I would take the word of the designers of the JMM over a non-specialist (in concurrency) such as Bruce Eckel that JMM is addressing these issues effectively.
I don't think Eckel's arguing that the JMM is flawed; he's arguing that writing correct shared-memory multithreaded code is so hard that in an application of nontrivial size almost everyone will make mistakes (insufficient synchronization, potential deadlocks), and some of those mistakes will manifest only on multi-core machines because on single-core machines, not all scenarios permitted by the JMM are possible.
Michael Borgwardt
That may be (and after a 2nd reading he does mention shared memory) , but your accepted answer is not addressing that specific issue: shared memory, and caching mechanisms. The issue is not general "synchronization". And if saying *"'write once, run everywhere' cannot possibly extend to threading on single vs. multicore machines"* is not arguing that JMM is flawed, then we seem to be confronting a shared language (English) issue :)
+5  A: 

People often liked to refer to it as "giving them enough rope to hang themselves". Some language paradigms, such as threads or pointers work well enough, except that they are extremely high sources of bugs.

Loose pointer errors in C and C++ are rampant, and so are caching/threading bugs in Java. You can lead a horse to water, but you can't convince it to read the manual, and you certainly can't convince it to code these types of things carefully and consistently. The best you can do is find an abstraction (like references) that covers it up nicely and takes away the control from the programmers. If you've got to add a "special" keyword like synchronized, you're already in trouble with the masses ....

Paul.

Paul W Homer
+1 for the horse reading the manual.
Thorbjørn Ravn Andersen
+5  A: 

The addition of java.util.concurrent and the complexity embodied in the (excellent) "Java Concurrency in Practice" make it clear that the synchronized keyword is far from sufficient.

I think that's the benefit of the Java EE app servers. They might be heavy and expensive, but they were an attempt to isolate the difficult code to handle multiple threads in a single place under the control of top-shelf developers. To the greatest degree possible, they tried to make it appear to a developer that they were writing in a single-threaded environment: the one that was handling the current request.

duffymo
could you elaborate or point to an overview? I'm just using Java SE but maybe there are concepts about Java EE that would be helpful to understand.
Jason S
Java EE app servers handle all the difficult work behind the scenes: queuing requests, matching them with worker threads, etc. When you write EJBs they're required to be single-threaded, because the container is handling that stuff for you.
duffymo
+2  A: 

If you can prove your program to be correct, then it will work regardless of the platform it's run on....unless there's a bug in the OS/JVM, but then that's not a bug in your program is it? The issue is that it's a LOT of work to prove a concurrent program correct (or any program for that matter), and it's very difficult to write correct concurrent programs beyond a certain level of complexity. I certainly think he went too far, but concurrency is hard.

Draemon
+6  A: 

As others have pointed out, synchronization (and other primitives) work flawlessly. What's tricky is building a correct larger system. Just marking things as synchronized doesn't make a system safe. You can introduce problems like deadlocks by synchronizing in the wrong way. The more complex a system you're designing, the more likely you'll run into higher-level gotchas like this.

There are some issues that will with near-zero probability show up on a single core machine. For example, a bug may only occur in some code under some very particular interleavings at the instruction level. Since the OS is very unlikely to switch tasks repeatedly on a process after only executing a few instructions, the "bad" interleavings may never actually occur on a single-core machine. On a multicore machine, the bad interleavings may happen nearly every time.

Just because a parallel program has run correctly N times does not mean that it's necessary correct and free of concurrency bugs.

You can write correct concurrent programs. It's just hard to do it for most of the non-trivial cases.

Mr Fooz
+2  A: 

Synchronized works well, but many things that you think should work, won't.

There are some completely bizarre cases where something you'd be absolutely certain to work concurrently won't AT ALL. This includes semaphores.

Remember, CPU threads can have different caches, so if a variable happens to be in a cache, you can run into some very strange problems.

Bill K
Or as I found out, linking a properties file to a Class. The method that I used was about 20 lines, everything was synchronized including the method and Class and yet I got concurrency issues. It seams that another method was calling the same properties file. yeesh!
WolfmanDragon
That's what memory barriers are for (syncrhonized).
Software Monkey
What is difficult to do is to shortcut/circumvent the use of synchronized.
Software Monkey
Synchronized will not help if there are multiple processes and/or classloaders contending for the same external resource (e.g. a file.) I'm guessing that's what happened here (e.g. 2 web apps on the same server sharing a configuration file)
finnw
+6  A: 

I think the main point here is, when Eckel's code was first written the different threads were theoretically being executed simultaneously. With the advent of multi-core machines the threads are now literally executing simultaneously.

Multi-core machines are like a magnifying glass, they make your concurrency bugs much larger.

bcash
Citation: The magnifying glass remark originates from ttp://stackoverflow.com/users/5812/bob-cross
bcash
+11  A: 

I think he is too pessimistic, and the problem is he is not using the proper tools.

If you use mostly immutable data structures and a design which encapsulates the uses of synchronization primitives in small parts of the code and rather use queues, workers etc., it is not so bad.

starblue
How easy is that to do in Java? It sounds like an approach that might be better suited for a different language.
David Thornley
Have to agree; and it's quite doable in Java with a little discipline and a methodical approach.
Software Monkey
As someone doing multi-core concurrency in Haskell, I'm of mixed minds about this. Yes, purity is a great help, But it's still not easy.And anyway, how many Java programmers even know what a persistent data structure is? They're certainly not offered in the libraries.
Curt Sampson
+3  A: 

Does Java (+ other languages/OS calls, for that matter) not offer tools to provide sufficient guarantees of concurrency-correctness to run real-world applications? This is not just a rhetorical question, [...]

To get a fresh perspective on concurrency in modern programming languages, I suggest you read http://clojure.org/state and http://clojure.org/concurrent_programming

What these essays have learned me is that sometimes you need to think outside of the box to better understand what's inside the box, the box being filled with traditional imperative OO languages here.

eljenso
A: 

Writing correct concurrent code is a very, very complicated thing. It's not that the tools are inadequate or don't work, it's that even with them it's possible - even easy - to write code that fails under some circumstances.

The huge number of papers on concurrency is not a sign that the problem is solved - it's a sign the the problem is not solved. Nobody writes papers on issues that are well understood.

DJClayworth
+2  A: 

You may find this interesting.

Towards Better Concurrency

Avrom
+3  A: 

Unfortunately, there are many ways to write broken Java concurrency code. I've been doing some blogging on some of the more common ways to screw it up. :)

In my experience, I'd say the meta-problem is that it's relatively easy to take two pieces of code, both of which are thread-safe, and put them together in a way that's broken. That means that it's hard to compose thread-safe programs when every time you bring new components together, you have to re-evaluate everything below it.

Alex Miller
A: 

One place that write-once, run-anywhere doesn't handle well is in performance tuning. Often I find that the same profiler will give different results on a windows PC and a linux server. Performance and race conditions are often a related problem.

So in practice you should test your application on each platform you expect to support, and you may find non-ideal differences, but you should be able to write a program which runs correctly on all platforms.

Peter Lawrey
+1  A: 

First of all, if you are using synchonized.

You may get subtle deadlocks because of the global lock on that instance which you can't be sure of; where it is taken.

For C#, there's a plugin model-checker: Chess. so if you feel like it you could start programming in C# on .Net/Mono and test it using this. The two languages are equal enough even though Java is a subset of C# (except the single case of anonymous classes).

Henrik
A: 

I would not be surprised if, sometime in the future, someone comes up with a proof to show that shared-memory concurrency programming is only possible in theory, but not in practice.

I wonder if this could be refering to Double Checked Locking: Wikipedia link.

I particularly like this quote:

Many versions of the double-checked locking idiom that do not use explicit methods such as volatile or synchronization to communicate the construction of the object have been proposed, and all of them are wrong.

... and also the following comment that says that the only way to fix it is to do the one thing that you were trying to avoid in the first place (i.e. use volatile or synchronized).

The interesting thing about Double Checked Locking is that it's hard to persuade even very competent developers not to use it, even after you've told them (with references) time and again that it is broken, and that there are ways to achieve the same objective that work. Perhaps it's because the explanation of how it is broken sounds insane to a logical mind? And of course they keep finding it as a solution on the web.

If so many experts could get it so wrong, for so long, on Double Checked Locking, there must be an argument that says "It could happen again."

richj
+2  A: 

The mechanisms provided by the language and VM (synchronized, AtomicReference) etc. usually work fine until you try to optimize your code by using them less. Double-checked locking is the most famous example, but there are lots of other common mistakes:

  • Trying to use volatile as a substritute for synchronized or AtomicReference
  • Inappropriate use of AtomicReference.weakCompareAndSet or AtomicReference.lazySet
  • Intentionally mixing synchronized and unsynchronized access to the same variable (Double-checked locking is case of this but there are many others.)
  • Rolling your own version of BlockingQueue instead of using the standard ones. The same applies to Exchanger and CountDownLatch.

Although you can break your code without attempting to optimize (e.g. deadlocking, unintentionally using different locks to guard the same variable), the majority of bugs I've seen have been caused by misguided attempts to optimize.

volatile is a huge code smell. If you suspect you have a concurrency bug the first thing I recommend you try is grep -r volatile *.java. The second thing to try is findbugs.

finnw