shaggy359
Photo credit: shaggy359
del.icio.us Digg DZone Reddit StumbleUpon
1 | 2 | 3 | 4 | 5 | 6 | Next »
Software Development

Concurrent Programming for Practicing Software Engineers

Learn several concurrent programming concepts that every professional software engineer needs to understand.

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:

  • Most problems don't require a strong understanding of concurrency. I'd even argue that many web applications, which are often highly concurrent, don't require a strong understanding of concurrency: sometimes there isn't significant data sharing; sometimes the burden of managing concurrency, isolation and deadlocks is pushed back to a transactional data store; sometimes the consequences of races are minor or completely insignificant; etc.
  • Even when a problem does require some knowledge of concurrency, a lack of knowledge manifests itself only sporadically and generally in ways that are difficult to reproduce, which allows people to mistake their 99.9999% solution for a 100% solution. Even in cases where the developer is under no such delusion, he may believe that his 99.9999% solution "works". He fails to notice that 99.9999% may not be so high when you're talking about long-running applications running on processors that execute billions of cycles per second, and that sometimes the consequences of that 1-in-1,000,000 screwup are disastrous.
  • The "behavioral" structure of a program, as embodied by its threads, is largely orthogonal to the program's "static" structure, as embodied by packages, classes and methods. Since code is organized around the latter, developers can "see" and understand it. Since code is not organized around the behavioral structure (for example, the code does not contain explicit representations of the paths of individual threads), developers have a harder time seeing and understanding it.

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.

What we'll cover

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.

Social bookmarks: del.icio.us Digg DZone Reddit StumbleUpon
1 | 2 | 3 | 4 | 5 | 6 | Next »

Comments (4)

Hi,

I like your article because you described the classic problems and solutions well. But there are some minor issues with the code:

1) the userMap in the InMemoryUserDao is not published safely and therefor relies on an external happens before rule to prevent reordering and visibility problems. This could lead to a NullPointerException when accessing the users reference. In most cases with constructors this is not an issue, but imho still a bad practice. It can be fixed by making users final. For more information see http://pveentjer.wordpress.com/2008/04/02/jmm-constructors-dont-have-visiblity-problems/

2) I understand that you are working towards a solution with the InMemoryUserDao and step by step solve the problem. But I would add the advice to less experienced readers that they should look at really concurrent structures like the ConcurrentMap. It has atomic operations for the check/act problems (like putIfAbsent) but implementations can also perform better because they use lock striping for example instead of a big lock.

3) Instead of using AND the internal synchronization of the SynchronizedMap AND the synchronization in the InMemoryUserDao, I would drop the SynchronizedMap completely and replace it by a standard Hashmap (make sure that all methods are going through the lock) It makes reasoning about locking easier. So if you can't use the ConcurrentMap (e.g. a non standard datastructure) just have one location where the locking is done.
By Peter Veentjer on Oct 13, 2008 at 9:01 AM PDT
Well-written clear article handling threading and concurrency. Outstanding at the conceptual level.

The implementation details will of course be language specific and pattern specific, and there exists more than one way to accomplish it.

The value to me in the article is the clear handling of the concepts of threading, concurrency, related issues, and strategies to manage them.
By Dave Milner on Oct 13, 2008 at 10:44 AM PDT
Great article Willie. C# programmers that may be interested in how to accomplish synchronized methods should look into decorating the methods with the MethodImpl attribute

[MethodImpl(MethodImplOptions.Synchronized)]

or just wrap the entire content of your method in a lock statement.

e.g.

public class MyClass
{
public void MyMethod()
{
lock(typeof(MyClass))
{
// contents of method
}
}
}
By Collin Couch on Oct 13, 2008 at 11:01 AM PDT
@Peter: Ah, good catch re #1. Yes, declaring users final will prevent consumer threads from seeing the DAO instance half-published (object created but state not initialized) by the publisher thread. I agree with both of your other points as well. In particular, I like your #3. Concurrency semantics are communicated through documentation more than through language features and so stylistic issues become important. Thanks for the feedback.
By Willie Wheeler on Oct 13, 2008 at 10:11 PM PDT

Post a comment

Your name:
Your e-mail address (won't be displayed):
Your web site (optional):
example: www.xyz.com
Your comment:
Preview:
By You
Please help us reduce comment spam:
Spring Annotations RefCard
Check out the new DZone Spring Annotations Refcard by Craig Walls!

What's New?

2008-12-14 - We've just submitted a few more chapters of the book for review, so we're about halfway done.
2008-10-20 - I've added a new mailing list feature to the site. Sign up to receive e-mail updates about new articles.
2008-09-30 - We've released chapter 4 (User registration) and chapter 5 (Authentication) of Spring in Practice.
2008-09-11 - By popular demand, I've added an RSS feed to the site.
Home | Consulting | Tech Articles | Mailing List | Contact | Spring Blog
Copyright © 2008 Wheeler Software, LLC.