- multi-threading is an ability of a program to execute multiple threads concurrently, where each thread represents an independent flow of execution.
- threads share the same memory space within a process, making them lightweight compared to separate processes.
- benefits -
- concurrency - perform multiple tasks simultaneosly
- responsiveness - keep application responsive
- efficiency - utilizes cpu more effectively
- modularity - break complex tasks into independent threads.
- challenges -
- race conditions.
- deadlocks - thread waiting for each other infinitely.
- thread safety - ensuring shared resources are accessed correctly.
- thread lifecycle -
- new - thread created but not started
- runnable - thread is ready to run or running
- blocked/waiting - thread is waiting for a monitor lock or another condition.
- timed waiting - waiting for a specific amount of time.
- terminated - thread has completed execution.
main thread
- it is the default thread created when a java program starts.
- created automatically by jvm.
- responsible for executing
main()method - can create and manage other threads.
- program terminates when the main thread ends.
public class MainThread {
public static void main(String[] args) {
Thread current = Thread.currentThread(); // Get main thread
System.out.println("Main thread: " + current.getName());
System.out.println("Priority: " + current.getPriority());
}
}
java thread model
it provides a robust thread model through java.lang.Thread class and java.lang.Runnnable interface. threads can be created in two ways -
- extending the
threadclass
class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(getName() + ": Count " + i);
}
}
}
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // Start thread
}
}
- implementing
runnableinterface
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + ": Count " + i);
}
}
}
public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable(), "RunnableThread");
t1.start();
}
}
key methods
start(): Begins thread execution (calls run()).run(): Contains the thread’s task (override in subclass or Runnable).sleep(long millis): Pauses the thread for the specified time.join(): Waits for the thread to terminate.setName(String name): Sets the thread’s name.getName(): Gets the thread’s name.setPriority(int priority): Sets the thread’s priority.isAlive(): Checks if the thread is running.
thread priorities
thread priorities determine the relative importance of threads, influencing the schedular’s decision on which thread to execute when multiple threads are runnable. Java assigns prirorites as integer from 1 to 10.
Thread t1 = new Thread(() -> System.out.println("Low priority"));
t1.setPriority(Thread.MIN_PRIORITY);
Thread t2 = new Thread(() -> System.out.println("High priority"));
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
synchronization
- when multiple threads access the same shared resources, race conditions can occur, leading to inconsistent or incorrect results.
- Synchronization ensures that only one thread can access the data once at a time.
- mechanism -
- synchronization methods - add
synchronizedkeyword to a method to ensure only one thread can execute at a time for a given object. uses the object’s intrinsic lock (monitor). - synchronized blocks - synchronize a specific block of code using an object’s lock
- synchronization methods - add
// synchronization method
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
// synchronization blocks
class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
}
interthread communication
- interthread communication allows threads to coordinate their actions, typically using the wait-notify mechanism to avoid busy-waiting (polling).
- this is useful when one thread needs to wait for another to complete a task or update a shared resource.
key methods
wait()- causes the current thread to wait until another thread callsnotify().notify()- wakes up one waiting threadnotifyall()- wakes up all waiting threads.
class SharedBuffer {
private int data;
private boolean available = false;
public synchronized void produce(int value) throws InterruptedException {
while (available) {
wait(); // Wait if buffer is full
}
data = value;
available = true;
System.out.println("Produced: " + data);
notifyAll();
}
public synchronized int consume() throws InterruptedException {
while (!available) {
wait(); // Wait if buffer is empty
}
available = false;
System.out.println("Consumed: " + data);
notifyAll();
return data;
}
}
public class Main {
public static void main(String[] args) {
SharedBuffer buffer = new SharedBuffer();
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
buffer.produce(i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
buffer.consume();
Thread.sleep(2000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}