Many professional software engineers see concurrent programming as something of an arcane art, and indeed it's possible to make it several years into a career without a good grasp of threads. The reasons seem clear enough:
There are times when you really do need to understand how threads work in order to implement required functionality or else avoid simple but potentially serious mistakes.
In this article we're going to cover several important concurrent programming concepts that every professional software developer needs to understand: serialism, parallelism, race conditions, synchronization, deadlocks and global lock orderings. The concepts form a logical progression that looks a little like an arms race: some problems lend themselves to a parallel solution, but parallelism gives rise to race conditions. We counter race conditions with synchronization (among other techniques), but synchronization leads to deadlocks (among other problems). And so we counter deadlocks with global lock orderings (among other techniques).
You'll note that I said "among other problems" and "among other techniques". The treatment here doesn't pretend to be comprehensive. There are lots of important topics in concurrent programming, such as proper encapsulation, contention-induced (rather than compute-induced) performance issues, read/write locking, minimizing lock scope, fine- vs. coarse-grained locks, timeouts, forking and joining and on and on (and on). I believe however that the topics we're about to treat provide a nice context against which developers can more easily learn other topics. I'll probably write about some of the other topics eventually.
By the end of the article you'll have a basic foundation in multithreaded programming, one that I consider to be required for practicing software engineers.
Our presentation is Java-centric, but developers from other languages (especially C#) should still find the discussion useful.