Java thread is a powerful feature that allows you to create and run multiple threads of execution within a single program. Threads are lightweight sub-processes that share the same memory space and can communicate with each other. Threads can improve the performance and responsiveness of your application by performing tasks in parallel or in the background. In this blog, I will show you how to create and use threads in Java with some examples.
There are two main ways to create threads in Java:
Extending the Thread class
You can create a subclass of the Thread class and override its run() method. The run() method contains the code that the thread will execute. You can create an instance of your subclass and call its start() method to start the thread.
// A subclass of Thread that prints a message
class MyThread extends Thread {
private String message;
// Constructor that takes a message as a parameter
public MyThread(String message) {
this.message = message;
}
// Override the run() method
public void run() {
// Print the message 20 times
for (int i = 0; i < 20; i++) {
System.out.println(message);
}
}
}
// Create and start two threads
public class ThreadDemo {
public static void main(String[] args) {
// Create two instances of MyThread with different messages
MyThread thread1 = new MyThread("Welcome to Learn with IH");
MyThread thread2 = new MyThread("Thank you for visiting my site");
// Start the threads
thread1.start();
thread2.start();
}
}
Implementing the Runnable interface
You can create a class that implements the Runnable interface and provide the implementation for its run() method. The run() method contains the code that the thread will execute. You can create an instance of your class and pass it to the constructor of the Thread class. You can then call the start() method of the Thread object to start the thread.
// A class that implements Runnable and prints a message
class MyRunnable implements Runnable {
private String message;
public MyRunnable(String message) {
this.message = message;
}
// Implement the run() method
public void run() {
// Print the message 20 times
for (int i = 0; i < 20; i++) {
System.out.println(message);
}
}
}
// Create and start two threads
public class RunnableDemo {
public static void main(String[] args) {
// Create two instances of MyRunnable with different messages
MyRunnable runnable1 = new MyRunnable("Welcome to Learn with IH");
MyRunnable runnable2 = new MyRunnable("Thank you for visiting my site");
// Create two Thread objects and pass the runnables to their constructors
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
// Start the threads
thread1.start();
thread2.start();
}
}
The main difference between the two ways is that when you extend the Thread class, you cannot extend any other class, but when you implement the Runnable interface, you can extend another class as well. Also, when you implement the Runnable interface, you can share the same Runnable object among multiple threads, which can be useful for resource sharing.
Control threads in Java
There are some methods and properties that you can use to control the behavior and state of threads in Java.
setName() and getName()
You can use these methods to set and get the name of a thread. The name can be useful for debugging or identification purposes.
Thread thread = new Thread();
thread.setName("ThreadName");
System.out.println(thread.getName());
setPriority() and getPriority()
Sometimes we need to set prioty of a thread. For this, you can use these methods to set and get the priority of a thread. The priority is a number between 1 and 10 that indicates how likely the thread is to get CPU time. The higher the priority, the more likely the thread is to run. The default priority is 5. You can also use the constants Thread.MIN_PRIORITY, Thread.NORM_PRIORITY, and Thread.MAX_PRIORITY to represent the minimum, normal, and maximum priority values.
Thread thread = new Thread();
thread.setPriority(Thread.MAX_PRIORITY);
System.out.println(thread.getPriority());
setDaemon() and isDaemon()
Daemon in thread is an importent concept. To use daemon, you can use these methods to set and check whether a thread is a daemon thread or not. A daemon thread is a thread that does not prevent the JVM from exiting when the main thread finishes. A non-daemon thread, on the other hand, keeps the JVM alive until it finishes. The default value is false, meaning that the thread is a non-daemon thread. You can only set the daemon status before the thread starts.
Thread thread = new Thread();
thread.setDaemon(true);
System.out.println(thread.isDaemon());
join()
This can be useful for synchronization or dependency purposes. You can use this method to wait for a thread to finish before continuing the execution of the current thread. You can also specify a timeout in milliseconds to limit the waiting time.
Thread thread = new Thread();
thread.start();
thread.join(); // wait until thread finishes
System.out.println("Thread finished"); // prints after thread finishes
interrupt() and isInterrupted()
Interrupting a thread means setting a flag that indicates that the thread should stop what it is doing and handle the interruption. The thread can check the flag by calling the isInterrupted() method or the static Thread.interrupted() method, which also clears the flag. The thread can also catch the Interrupted Exception, which is thrown when the thread is blocked and interrupted.
Thread thread = new Thread() {
public void run() {
try {
// Do some work that can be interrupted
Thread.sleep(1000);
} catch (InterruptedException e) {
// Handle the interruption
System.out.println("Thread interrupted");
}
}
};
thread.start();
thread.interrupt(); // interrupt the thread
Synchronize threads in Java
One of the challenges of multithreading is to ensure that multiple threads can access shared resources without causing inconsistency or corruption. This is called thread synchronization or thread safety. There are some mechanisms that you can use to achieve thread synchronization in Java.
Synchronized keyword
You can use this keyword to mark a block of code or a method as synchronized. This means that only one thread can execute the synchronized code at a time. The other threads have to wait until the current thread releases the lock on the object that the synchronized code belongs to.
public void newMethod1() {
// Synchronize a block of code
synchronized (this) {
//statements
}
}
// Synchronize the whole method
public synchronized void newMethod2() {
//statements
}
Volatile keyword
By marking a variable as volatile, the value of the variable is always read from and written to the main memory, not the cache. This ensures that the threads see the most updated value of the variable. However, this does not guarantee atomicity, meaning that the read-modify-write operations on the variable are not thread-safe.
private volatile boolean flag = true;
Atomic classes
The atomic classes in the java.util.concurrent.atomic package uses to perform thread-safe operations on primitive types and references. These classes use low-level techniques, such as compare-and-swap, to ensure atomicity and visibility without locking.
private AtomicInteger count = new AtomicInteger(0);
// Use the incrementAndGet() method to atomically increment and return the count
public int increment() {
return count.incrementAndGet();
}
public int getCount() {
return count.get();
}
Java threading is a powerful feature that allows you to create and run multiple threads of execution within a single program. Threads can improve the performance and responsiveness of your application by performing tasks in parallel or in the background. You can create threads by extending the Thread class or implementing the Runnable interface. You can control threads by using methods and properties, such as setName, setPriority, join, interrupt, and isDaemon. You can synchronize threads by using mechanisms, such as synchronized, volatile, and atomic classes.
Referances:
- https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html