Java Concurrency in Practice - Highlights
Since only one thread at a time can execute a block of code guarded by a given lock, the synchronized blocks guarded by the same lock execute atomically with respect to one another. In the context of concurrency, atomicity means the same thing as it does in transactional applications - that a group of statements appear to execute as a single, indivisible unit. No thread executing a synchronized block can observe another thread to be in the middle of a synchronized block guarded by the same lock.
Reentrancy means that locks are acquired on a per-thread rather than pre-invocation basis.
Every shared, mutable variable should be guarded by exactly one lock. Make it clear to maintainers which lock that is.
While synchronized methods can make individual operations atomic, additional locking is required when multiple operations are combined into a compound action. At the same time, synchronizing every method can lead to liveness or performance problems, as we saw in SynchronizedFactorizer.
Acquiring and releasing a lock has some overhead, so it is undesirable to break down synchronized blocks too far, even if this would not compromise atomicity.
Avoid holding locks during lengthy computations or operations at risk of not completing quickly such as network or console I/O.
It's the mutable state, stupid.
Make fields final unless they need to be mutable.
Immutable objects are automatically thread-safe.
Immutable objects simplify concurrent programming tremendously. They are simpler and safer, and can be shared freely without locking or defensive copying.
Encapsulation makes it practical to manage the complexity.
Guard each mutable variable with a lock.
Guard all variables in an invariant with the same lock.
Hold locks for the duration of compound actions.
A program that accesses a mutable variable from multiple threads without sync is a broken program.
Include thread safety in the design process.
Document your synchronization policy.