Creating Threads, Thread Synchronization & Thread Scheduling
1. Two Ways to Create a Thread in Java
Java provides two ways to create and launch a new thread of execution:
- By extending the
Threadclass (class MyThread extends Thread) - By implementing the
Runnableinterface (class MyTask implements Runnable)
class MyThread extends Thread- Override
public void run() - Instantiate:
MyThread t = new MyThread(); - Start thread:
t.start();
class MyTask implements Runnable- Implement
public void run() - Pass to Thread:
Thread t = new Thread(new MyTask()); - Start thread:
t.start();
Method 1: Creating a Thread by Extending the Thread Class
- Create a class that
extends java.lang.Thread. - Override the
public void run()method and write the code you want the thread to execute inside it. - Create an object of your class and call
start()(which creates a new thread call stack and automatically invokesrun()).
class NumberThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(getName() + " printing: " + i);
}
}
}
public class ThreadClassDemo {
public static void main(String[] args) {
NumberThread t1 = new NumberThread();
NumberThread t2 = new NumberThread();
t1.setName("Worker-1");
t2.setName("Worker-2");
t1.start(); // Starts Worker-1 concurrently
t2.start(); // Starts Worker-2 concurrently
}
}
Method 2: Creating a Thread by Implementing the Runnable Interface
- Create a class that
implements java.lang.Runnable. - Implement the single abstract method
public void run(). - Create an object of your class, pass it into the constructor of a
Threadobject (Thread t = new Thread(taskObj);), and callt.start().
class DownloadTask implements Runnable {
@Override
public void run() {
for (int step = 1; step <= 3; step++) {
System.out.println(Thread.currentThread().getName() + " - Downloading chunk " + step);
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
DownloadTask task = new DownloadTask();
Thread t1 = new Thread(task, "Downloader-A");
Thread t2 = new Thread(task, "Downloader-B");
t1.start();
t2.start();
}
}
Why do we call
t.start()instead of callingt.run()directly? If you callt.run()directly, no new thread is created—run()is executed like a normal method call on the currentmainthread stack! Callingt.start()instructs the JVM to create a separate thread call stack and then invokerun()concurrently.
Comparison: Extending Thread vs Implementing Runnable (Which is Better?)
Implementing Runnable is always preferred in real-world Java programming! Why?
| Basis | Extending Thread Class |
Implementing Runnable Interface |
|---|---|---|
| 1. Multiple Inheritance | Because Java does not allow extending two classes, if your class extends Thread, it cannot extend any other class (like Frame or Applet). |
Your class can still extend another parent class (class MyApplet extends Frame implements Runnable). |
| 2. Object Sharing | Each thread creates a separate unique object. | Multiple threads can share the exact same Runnable target object. |
| 3. OOP Design | Tightly couples task logic with thread control. | Cleanly separates the Task (Runnable) from the Runner (Thread). |
2. Thread Synchronization in Java
When two or more threads try to access and modify a shared resource (such as the same Bank Account balance or Printer object) at the exact same time, their operations can overlap and corrupt the data. This bug is called a Race Condition.
Real-Life Example of Race Condition: Suppose a joint bank account has ₹10,000. Husband and Wife both swipe their ATM cards at the exact same millisecond to withdraw ₹8,000 each. Without synchronization, both threads read
balance = 10000, see that10000 >= 8000, and dispense ₹16,000 total!
What is Thread Synchronization?
Thread Synchronization is the mechanism that ensures only ONE thread can access a shared resource (critical section) at a time.
- In Java, every object has an internal lock called a Monitor (or Intrinsic Lock).
- When a method or block is declared with the
synchronizedkeyword, a thread must first acquire the object’s Monitor Lock before entering. All other threads trying to enter are blocked until the first thread finishes and releases the lock!
Complete Program Demonstrating synchronized Method
class BankAccount {
private int balance = 10000;
// 'synchronized' ensures only ONE thread can withdraw at a time!
public synchronized void withdraw(String user, int amount) {
System.out.println(user + " is trying to withdraw Rs." + amount);
if (balance >= amount) {
balance -= amount;
System.out.println("SUCCESS: " + user + " withdrew Rs." + amount + " | Remaining: Rs." + balance);
} else {
System.out.println("FAILED: Insufficient balance for " + user + " | Available: Rs." + balance);
}
}
}
public class SyncDemo {
public static void main(String[] args) {
BankAccount sharedAcc = new BankAccount();
// Thread 1 (Person A) and Thread 2 (Person B) accessing the SAME account
Thread t1 = new Thread(() -> sharedAcc.withdraw("Aarav", 8000));
Thread t2 = new Thread(() -> sharedAcc.withdraw("Priya", 8000));
t1.start();
t2.start();
}
}
Output (Protected against Race Condition):
Aarav is trying to withdraw Rs.8000
SUCCESS: Aarav withdrew Rs.8000 | Remaining: Rs.2000
Priya is trying to withdraw Rs.8000
FAILED: Insufficient balance for Priya | Available: Rs.2000
3. Thread Scheduling and Thread Priority
When multiple threads are waiting in the Runnable queue, who decides which thread gets the CPU next? The Thread Scheduler (part of the JVM) makes this decision using two algorithms:
- Preemptive-Priority Scheduling: The thread with the highest priority number is chosen to run first. If a higher-priority thread enters the Runnable state, it preempts (interrupts) the currently running lower-priority thread.
- Time-Slicing (Round-Robin): If multiple threads have the exact same priority, each thread is given a fixed slice of CPU time in turn.
Thread Priorities in Java (1 to 10)
Every thread in Java is assigned an integer priority between 1 (Lowest) and 10 (Highest). By default, every thread inherits the priority of its parent thread (main has priority 5).
The Thread class defines 3 built-in priority constants:
Thread.MIN_PRIORITY=1(Lowest priority)Thread.NORM_PRIORITY=5(Default normal priority)Thread.MAX_PRIORITY=10(Highest priority)
Methods to Get and Set Thread Priority:
int getPriority(): Returns the thread’s current priority.void setPriority(int newPriority): Sets the thread’s priority (1to10; throwsIllegalArgumentExceptionif outside1–10).
t1.setPriority(Thread.MIN_PRIORITY); // Sets priority to 1
t2.setPriority(Thread.MAX_PRIORITY); // Sets priority to 10
💡 Exam Preparation Tips for DAVV BCA
- If asked “Why is implementing
Runnablebetter than extendingThread?”, highlight that Java does not support multiple inheritance of classes, soimplements Runnableleaves your class free toextendanother class! - Always remember the 3 priority constants:
MIN_PRIORITY = 1,NORM_PRIORITY = 5, andMAX_PRIORITY = 10.