Herb Sutter has been doing a lot of work on this stuff over the last 10 years and his blog is full of stuff on what you should do... it's not too nitty gritty in terms of languages and stuff, but it's very informative in terms of understanding the issues and what not. Check out http://herbsutter.wordpress.com/.
Some rules of thumb that I've found useful:
- Hide mutexes and locks at (nearly) all costs. If you have a queue class, for example, that has a locking push() function, and someone needs to lock for a series of pushes, don't expose the lock to let them lock things for the series of pushes, but provide a push function that takes a list of items instead. Keep thinking of ways to hide your locking strategies. If your class is deadlock-free then you can be reasonably sure (I've always said "reasonably" but I've never seen it not work either) that you'll never see a deadlock in real life either. Race conditions are a different story, however.
- Trying to figure out a solution where you never have to think about the concurrency of things is a scary place to go... Have a logical concurrent model instead. For example, if you work with user's and user's get events, rather than just letting them process any number of events in parallel, it may be reasonable to sequence events per-user and let the users run in parallel.
- If you do have to expose locks, use a locking hierarchy. Herb shows this here: http://herbsutter.wordpress.com/2007/12/11/effective-concurrency-use-lock-hierarchies-to-avoid-deadlock/
- Avoid any concept of being impolite among your threads (i.e. forced interrupts or kills). Be polite. Herb has this here: http://herbsutter.wordpress.com/2008/04/10/effective-concurrency-interrupt-politely/
- Locking sucks, but it's necessary. If you think you can get away without having to lock in a dubious situation, you're probably wrong.
- Unit test, unit test, unit test. If your classes hide all of your locks, then unit tests cover a ton of cases.
I believe that following strict OO guidelines is even more important when dealing with concurrency than when dealing with general ideas in software... and let's face it, it's extremely important even when not dealing with concurrency