Why this matters
The bug. Whenever multiple locks are involved, every code path must take them in the *same* order. append goes writer → list; drain goes list → writer. The interleaving 'A grabs writer, B grabs list, both wait' is rare in development and inevitable in production.
The fix. Pick a canonical order — alphabetical, declaration order, doesn't matter — and apply it everywhere. Tools like SpotBugs (rule IS2_INCONSISTENT_SYNC) and ThreadSanitizer catch this in CI when test coverage is good enough.
Better architecture. Two locks usually means the data model is wrong. A single ReentrantLock or a ConcurrentLinkedQueue for the message list eliminates the lock-ordering problem entirely.
Review heuristic
Any function that acquires more than one lock, holds onto the first while waiting for the second, and isn't ordered consistently with every other function that touches those locks — that's a deadlock waiting for the right two requests to arrive together.
External reference: CWE-833: Deadlock.
↳ CWE-833; dining philosophers.