A thread is the smallest unit of execution in a program. In Java, a thread can be thought of as an independent path of execution within a process. Each thread has its own call stack, program counter, and local variables. A Java program can have multiple threads running concurrently, which means that different parts of the program can execute at the same time.
A process is a program in execution. It can contain one or more threads. For example, when you run a Java application, it is a process, and it can have multiple threads working together to perform different tasks.
In Java, a thread can be in one of the following states:
Thread
ClassThe first way to create a thread in Java is by extending the Thread
class. You need to override the run()
method, which contains the code that will be executed by the thread. Here is the basic syntax:
class MyThread extends Thread {
@Override
public void run() {
// Code to be executed by the thread
System.out.println("This is a thread created by extending Thread class.");
}
}
To start the thread, you need to create an instance of the MyThread
class and call the start()
method:
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
Runnable
InterfaceThe second way to create a thread is by implementing the Runnable
interface. You need to implement the run()
method in the class that implements the Runnable
interface. Here is the basic syntax:
class MyRunnable implements Runnable {
@Override
public void run() {
// Code to be executed by the thread
System.out.println("This is a thread created by implementing Runnable interface.");
}
}
To start the thread, you need to create an instance of the MyRunnable
class and pass it to the Thread
class constructor, and then call the start()
method:
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
When multiple threads access shared resources, there is a risk of data inconsistency. Synchronization is used to ensure that only one thread can access a shared resource at a time. In Java, you can use the synchronized
keyword to achieve synchronization. Here is an example:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
In the above example, the increment()
method is synchronized, which means that only one thread can call this method at a time.
The join()
method is used to wait for a thread to complete its execution. This is useful when you want to ensure that one thread has finished its task before another thread starts. Here is an example:
class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread has finished its execution.");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread can continue now.");
}
}
ExecutorService
FrameworkThe ExecutorService
framework in Java provides a high - level way to manage threads. It allows you to create a pool of threads and submit tasks to them. This is more efficient than creating and managing threads manually. Here is an example:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("Task is being executed by a thread from the pool.");
}
}
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executor.submit(new MyTask());
}
executor.shutdown();
}
}
stop()
, suspend()
, and resume()
The stop()
, suspend()
and resume()
methods in the Thread
class are deprecated because they can lead to inconsistent states in the program. Instead, use other mechanisms such as flags to control the execution of a thread.
When working with threads, it is important to handle exceptions properly. Unhandled exceptions in a thread can cause the thread to terminate unexpectedly. You can use try - catch blocks in the run()
method to handle exceptions.
class MessagePrinter implements Runnable {
private String message;
public MessagePrinter(String message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(new MessagePrinter("Hello"));
Thread thread2 = new Thread(new MessagePrinter("World"));
thread1.start();
thread2.start();
}
}
ExecutorService
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Task implements Runnable {
private int taskId;
public Task(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Task " + taskId + " is being executed.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " has completed.");
}
}
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executor.submit(new Task(i));
}
executor.shutdown();
}
}
Java multi - threading is a powerful feature that allows you to write more efficient and responsive applications. By understanding the basic concepts, usage methods, common practices, and best practices, you can start using multi - threading in your Java programs. Remember to handle shared resources carefully, use synchronization when necessary, and follow the best practices to avoid common pitfalls. With practice, you will be able to write complex multi - threaded applications that can take full advantage of modern multi - core CPUs.