Thread
ClassRunnable
InterfaceExecutorService
A thread is the smallest unit of execution within a process. In Java, a thread is an instance of the java.lang.Thread
class. Each thread has its own call stack, program counter, and local variables. Multiple threads can run within a single Java program, allowing for concurrent execution of different parts of the code.
Java threads can be in one of the following states:
Thread
object is created but the start()
method has not been called.start()
method is called, the thread is in the runnable state. It is waiting to be scheduled by the thread scheduler to run on the CPU.wait()
, join()
, or park()
. It will remain in this state until another thread wakes it up.sleep(long millis)
or wait(long timeout)
.run()
method has completed execution or an unhandled exception has occurred.Thread
Classclass MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running: " + Thread.currentThread().getName());
}
}
public class ThreadClassExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
In this example, we create a new class MyThread
that extends the Thread
class. We override the run()
method, which contains the code that the thread will execute. Then we create an instance of MyThread
and call the start()
method to start the thread.
Runnable
Interfaceclass MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running: " + Thread.currentThread().getName());
}
}
public class RunnableInterfaceExample {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
Here, we create a class MyRunnable
that implements the Runnable
interface. We implement the run()
method. Then we create an instance of MyRunnable
and pass it to the Thread
constructor. Finally, we call the start()
method on the Thread
object.
ExecutorService
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("Task is running: " + Thread.currentThread().getName());
}
}
public class ExecutorServiceExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i = 0; i < 5; i++) {
executor.submit(new MyTask());
}
executor.shutdown();
}
}
The ExecutorService
is a high - level concurrency utility in Java. In this example, we create a fixed - size thread pool with two threads using Executors.newFixedThreadPool(2)
. We submit five tasks to the executor service, and it will manage the execution of these tasks using the available threads in the pool. Finally, we call the shutdown()
method to gracefully shut down the executor service.
Synchronization is used to ensure that only one thread can access a shared resource at a time. In Java, we can use the synchronized
keyword.
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizationExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count: " + counter.getCount());
}
}
In this example, the increment()
method is declared as synchronized
. This ensures that only one thread can execute this method at a time, preventing race conditions.
Thread - safe classes are classes that can be safely used by multiple threads without any additional synchronization. Immutable classes, for example, are inherently thread - safe because their state cannot be changed after creation.
final class ImmutableClass {
private final int value;
public ImmutableClass(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
The ImmutableClass
is thread - safe because its state is immutable. Multiple threads can safely access the getValue()
method without any synchronization issues.
Java provides methods like wait()
, notify()
, and notifyAll()
for inter - thread communication.
class Message {
private String msg;
private boolean empty = true;
public synchronized String read() {
while (empty) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
empty = true;
notifyAll();
return msg;
}
public synchronized void write(String msg) {
while (!empty) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
empty = false;
this.msg = msg;
notifyAll();
}
}
public class InterThreadCommunicationExample {
public static void main(String[] args) {
Message msg = new Message();
Thread writer = new Thread(() -> {
msg.write("Hello, World!");
});
Thread reader = new Thread(() -> {
System.out.println(msg.read());
});
writer.start();
reader.start();
}
}
In this example, the Message
class uses wait()
and notifyAll()
methods to coordinate the reading and writing operations between two threads.
Deadlocks occur when two or more threads are blocked forever, each waiting for the other to release a resource. To avoid deadlocks, we can follow these rules:
Threads should properly manage the resources they use. For example, if a thread opens a file or a network connection, it should close them properly when they are no longer needed. We can use try - with - resources statements in Java to ensure proper resource management.
Java provides many high - level concurrency utilities like ExecutorService
, Semaphore
, CountDownLatch
, etc. These utilities are designed to simplify multithreading programming and reduce the chances of errors. For example, using ExecutorService
to manage thread pools can avoid creating too many threads, which can lead to performance degradation.
Multithreading in Java is a powerful feature that can significantly improve the performance and responsiveness of applications. By understanding the fundamental concepts, usage methods, common practices, and best practices, developers can write efficient and reliable multithreaded programs. However, multithreading also introduces challenges such as race conditions, deadlocks, and resource management issues. Therefore, it is important to follow best practices and use high - level concurrency utilities provided by Java.