Java Multitasking  «Prev  Next»

Lesson 9Synchronization
ObjectiveDescribe how and when to use synchronization in threads.

Java Thread Synchronization

When you synchronize methods, you are indicating that only one thread can use that method at a time. If another thread wants to use a synchronized method currently in use, that thread must wait. Usually this works fine, but there is one scenario in which this can be disastrous and that is deadlock.
Deadlock:
Imagine that there are two threads, A and B. Thread A is waiting to access a synchronized method in thread B. However, this method is in use by thread B, so thread A must wait. Thread B, in turn, is waiting to access a synchronized method in thread A, and this method in thread A is also in use. Both thread A and thread B are waiting on each other. This is what is meant by a deadlock.



Java Concurrency
Synchronization allows your program to ensure that independent threads do not interfere with each other. The Town Hall application builds synchronization into its methods. In the next few pages, we will look at:
  1. Why synchronization is good in the town hall application
  2. How Java Performs synchronization
Coordinating Java Threads - Quiz

Using join() method with Java Threads

In Java 1.1, the `join()` method in the Thread class is used to ensure that one thread completes its execution before another continues. This method allows a thread to wait for another thread to finish before proceeding, which is useful for synchronization.
How `join()` Works in Java 1.1 Threading Model
  • Introduced in Java 1.0 and continued in Java 1.1, the join() method allows one thread to wait for another thread's completion.
  • The thread that calls join() pauses execution until the target thread terminates.
  • It is part of cooperative threading, where the developer manually controls execution flow rather than relying on preemptive scheduling.

Usage Example in Java 1.1
class MyThread extends Thread {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - " + i);
            try {
                Thread.sleep(500); // Simulate work
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted");
            }
        }
    }
}

public class JoinExample {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        
        t1.start();
        try {
            t1.join(); // Main thread waits for t1 to finish before starting t2
        } catch (InterruptedException e) {
            System.out.println("Main thread interrupted");
        }

        t2.start(); // Starts only after t1 completes
        
        try {
            t2.join(); // Ensures main thread waits for t2 to complete before exiting
        } catch (InterruptedException e) {
            System.out.println("Main thread interrupted");
        }

        System.out.println("Main thread exiting");
    }
}


Behavior in Java 1.1 Threading Model
  1. Sequential Execution Control:
    • The main thread starts t1 and waits for it to finish using t1.join().
    • Only after t1 completes does t2 start.
  2. Blocking Mechanism:
    • The calling thread (main thread) blocks until the target thread (t1 or t2) completes.
  3. Thread Scheduling:
    • Java 1.1 did not have thread priorities enforced by the JVM, so developers relied on join() for proper execution sequencing.
  4. Cooperative Multitasking:
    • In Java 1.1, thread scheduling was less preemptive, meaning threads relied on methods like yield(), sleep(), and join() to manage execution order.

join(timeout) in Java 1.1:
  • A variant of join() allows a timeout value.
  • This ensures that if the thread does not complete within the specified time, execution continues.

t1.join(1000); // Wait for t1 for 1 second before continuing

Key Takeaways
  • Thread Synchronization: Ensures ordered execution.
  • Blocking Behavior: Calling thread waits for the target thread to finish.
  • Cooperative Threading: Before Java 1.2, JVM did not enforce strict thread priorities, so join() helped control execution order.
  • Alternatives: Before Java 5 (which introduced Executors and CountDownLatch), join() was a common way to handle thread dependencies.



Thread Question discussing the join() method

What can be done so that the following program prints "tom", "dick" and "harry" in that order?
public class TestClass extends Thread{
 String name = "";
 public TestClass(String str)  {
   name = str;
 }
 public void run() {
  try{
    Thread.sleep( (int) (Math.random()*1000) );
    System.out.println(name);
  }
  catch(Exception e){
  }
 }

 public static void main(String[] str) throws Exception{
   //1
   Thread t1 = new TestClass("tom");
   //2
   Thread t2 = new TestClass("dick");
   //3
   t1.start();
   //4
   t2.start();
   //5
   System.out.println("harry");
   //6
 }
} 

Select 1 option:
  1. Insert t1.join(); and t2.join(); at //4 and //5 respectively.
  2. Insert t2.join() at //5
  3. Insert t1.join(); t2.join(); at //6
  4. Insert t1.join(); t2.join(); at //3
  5. Insert t1.join() at //5


Answer: a
Explanation:
Here, you have 3 threads in action. The main thread and the 2 threads that are created in main. The concept is when a thread calls join() on another thread, the calling thread waits till the other thread dies.
If we insert t1.join() at //4, the main thread will not call t2.start() till t1 dies. So, in affect, "tom" will be printed first and then t2 is started. Now, if we put t2.join() at //5, the main thread will not print "harry" till t2 dies. So, "dick" is printed and then the main thread prints "harry".

SEMrush Software