Multithreading and Concurrency Concepts

Critical Section and Race Conditions

  • Critical section: A code block where multiple threads access shared resources, potentially causing concurrency issues.
  • Race condition: Occurs when multiple threads access a critical section with unpredictable execution sequences.

Avoiding Race Conditions

  • Blocking solutions: Use synchronized and locks.
  • Non-blocking solutions: Utilize atomic variables.

Synchronized

  • Synchronized uses an object lock, allowing only one thread to enter a “room” at a time.
  • Ensures atomicity within the critical section.
  • Methods with synchronized on member methods lock the this object.
  • Methods with synchronized on static methods lock the class object.

Thread Safety for Shared Resources

  • Ensure thread safety for shared variables (e.g., member and static variables) when read/written by multiple threads.
  • Pay attention to the scope of local variables.

Thread-Safe Classes

  • Certain classes in Java are thread-safe by design, such as String, Integer, StringBuffer, Random, Vector, Hashtable, and classes in java.util.concurrent.

Monitors and Object Header

  • Every Java object is associated with a Monitor object.
  • Locking an object involves modifying its header to point to a Monitor.
  • Only one thread can be the owner of a Monitor.

Lightweight Locking

  • Optimizes for scenarios where contention is minimal.
  • Utilizes CAS (Compare-And-Swap) operations for synchronization.

Lock Escalation (Lock Inflation)

  • Occurs when lightweight locking fails due to contention.
  • Transitions from lightweight to heavyweight locks.

Self-Spinning (Spin Locks)

  • Threads may spin (loop without blocking) while waiting for a lock.
  • Spinning can be more efficient than blocking in certain situations.

Biased Locking

  • Aims to reduce lock overhead when there’s minimal contention.
  • Threads bias a lock toward themselves to avoid CAS operations.
  • Objects start with unbiased locking and become biased over time.

Lock Coarsening

  • Combines multiple locks into a single lock to reduce synchronization overhead.
  • Increases concurrency but may impact granularity.

Lock Elimination

  • JIT compiler optimizations may remove locks if they are deemed unnecessary.

Wait and Notify

  • Used for thread coordination.
  • wait makes a thread wait, releasing the lock.
  • notify/notifyAll wakes up waiting threads.

Sleep vs. Wait

  • sleep waits for a specified time without releasing the lock.
  • wait releases the lock and waits for a notification.

Guarded Suspension Pattern

  • A pattern for a thread to wait for another thread’s result.

LockSupport

  • Park and unpark threads.
  • Provides fine-grained control over thread suspension.

Thread States

  • Threads can transition between states: NEW, RUNNABLE, WAITING, TIMED_WAITING, BLOCKED, TERMINATED.
  • Transition examples explained.

Multiple Locks

  • Using multiple locks to increase concurrency.
  • May lead to deadlocks if not managed correctly.

Deadlocks

  • Occur when threads wait for each other to release locks.
  • Monitor and resolve deadlocks using tools like jconsole or jstack.

Starvation

  • Occurs when a thread doesn’t get CPU time due to low priority.
  • Fair locks may reduce starvation.

ReentrantLock

  • A flexible lock supporting features like interruptibility, timeouts, and fairness.
  • Can be used with multiple conditions.

Interruptible Locking

  • ReentrantLock supports interruptible locking using lockInterruptibly.

Timeout Locking

  • ReentrantLock supports locking with a timeout using tryLock.

Fair Locks

  • Can be created using ReentrantLock for fair scheduling.
  • Guarantees a first-come-first-served order.

Condition Variables

  • Used for finer-grained thread synchronization.
  • Multiple conditions can be created for different aspects of synchronization.

Example:

ReentrantLock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();

lock.lock();
try {
    condition1.await();
    // ...
    condition2.signal();
} finally {
    lock.unlock();
}