The 30 second answer
A read-write lock lets many readers proceed together while a writer gets exclusive access, so it helps when reads vastly outnumber writes and each read takes a while. Otherwise a plain lock is simpler and often faster. A blocking queue is the standard producer-consumer handoff: producers put, consumers take, and a bounded capacity gives you backpressure.
ReadWriteLock. ReentrantReadWriteLock holds a pair of locks: many threads may hold the read lock at once, the write lock is exclusive. It pays off for a structure read constantly and rebuilt rarely, such as an in-memory routing table or a configuration snapshot, where readers do real work under the lock. Costs and caveats from the Javadoc and practice:
- It does not impose reader or writer preference by default; in non-fair mode a continuously contended lock can postpone some threads indefinitely. A fair mode exists at a throughput cost.
- A thread holding the read lock cannot upgrade to the write lock; trying deadlocks. Downgrading, acquiring the read lock before releasing the write lock, is allowed.
- If writes are frequent or critical sections tiny, the extra bookkeeping makes it slower than a single
ReentrantLock.
Alternatives: an immutable snapshot swapped through a volatile or AtomicReference field gives readers no locking at all; StampedLock offers optimistic reads.
BlockingQueue. The Javadoc says implementations are designed primarily for producer-consumer queues. put blocks when a bounded queue is full and take blocks when it is empty, so a slow consumer slows producers instead of exhausting memory. offer and poll with timeouts avoid blocking forever. A BlockingQueue does not accept null, because null signals failure from poll. Choose ArrayBlockingQueue for a fixed bound, LinkedBlockingQueue with an explicit capacity, or SynchronousQueue for direct handoff.
Example use. An indexing worker that reads change events, batches them, and sends bulk requests: producers put events on a bounded queue, one or more consumers drain up to N events or wait up to T milliseconds, then send a batch. When the search cluster slows down, the queue fills and the producers slow down too.