Skip to main content
Advertisement

Thread Synchronization and Control

In a multi-threading environment, unexpected problems can arise because multiple threads share the same resources (memory, etc.) of a process. To solve this, preventing other threads from accessing a specific resource until a thread completes its task is called Synchronization.

1. The synchronized Keyword

By setting a Critical Section on an entire method or a specific block, you can ensure that only one thread executes this area at a time.

Applying synchronized to a Method

public synchronized void withdraw(int money) {
if(balance >= money) {
try { Thread.sleep(1000); } catch(Exception e) {}
balance -= money;
}
}

Applying synchronized to a Specific Block

public void withdraw(int money) {
synchronized(this) { // Only the thread that acquires the lock of this object enters the block
if(balance >= money) {
try { Thread.sleep(1000); } catch(Exception e) {}
balance -= money;
}
}
}

Using synchronization like this prevents race conditions and protects data integrity.

2. wait() and notify()

While synchronization solves one problem, another issue can arise if a thread monopolizes a Lock for a long time, preventing other threads from progressing (known as Starvation).

To address this, the thread control methods wait() and notify() are used.

  • wait(): Places the currently executing thread into a waiting state and releases the object's lock.
  • notify(): Wakes up one of the waiting threads, making it runnable.
class Table {
// If the dish list is full, the cook must stop cooking and wait
public synchronized void add(String dish) {
if(dishes.size() >= MAX_FOOD) {
try {
wait(); // COOK thread waits
} catch(InterruptedException e) {}
}
dishes.add(dish);
notify(); // Dish added, wake up CUSTOMER thread
}
}

3. Thread Lifecycle and Control Methods

Threads have states, and their execution can be controlled using several essential methods.

  • sleep(long millis): Puts the current thread into a paused state for the specified time.
  • join(): Waits until another thread finishes its execution.
  • interrupt(): Wakes up a thread that is paused (by sleep, wait, join) and puts it back into a runnable state.
  • yield(): Yields its allocated execution time to the next thread.
// Example of measuring the work time of two threads using join()
Thread t1 = new Thread(new Worker());
Thread t2 = new Thread(new Worker());

t1.start();
t2.start();

long startTime = System.currentTimeMillis();

try {
t1.join(); // main thread waits for t1 to complete
t2.join(); // main thread waits for t2 to complete
} catch (InterruptedException e) {}

System.out.print("Time taken: " + (System.currentTimeMillis() - startTime));

Understanding these synchronization and flow control tools accurately is the core of Java multi-thread programming.

Advertisement