Introduce
In this article, we will learn about Thread, Multi Thread, Daemon Thread, Deadlock, Life Cycle…. understand and apply thread impractical. GetGoooo…..
- The content of the article is partially supported by ChatGPT
Define
Thread is a unique property of Java. It is the smallest unit of executable code that does a particular job. The Java language and the Java virtual machine are both threaded systems.
Multi Thread
- Java supports multithreading, which is capable of working with multiple threads. An application can contain multiple threads
- Multithreading keeps the idle time of the system to a minimum (ie squeeze it out =))). This allows you to write highly efficient programs with maximum CPU utilization. Each part of the program is called by a thread, each thread defining a different path of execution. This is a specialized design of multitasking.
- In multitasking, multiple programs run concurrently, each with at least one thread in it. A microprocessor executes all programs. Although it may appear that programs are being executed concurrently, the processor is in fact jumping back and forth between processes.
The theory will be like that, let’s practice together to understand better about Thread!
Create and manage Threads
When Java programs are executed, the main thread is always being executed, it is created automatically when you start the program, sub-threads will usually be created through it, it is also a thread. the last drug of the program
To create a Thread, there are usually 2 ways
- Using Thread
1 2 3 4 5 6 | Class HanhGa extends Thread { public void run() { //thực thi } } |
2.Using Runnable
1 2 3 4 5 6 | Class HanhGa implements Runnable { public void run() { //thực thi } } |
There are few differences when you use Runnable and Thread
- Runnable is an interface in Java, and Thread is a class that implements Runnable. If you want to create a new thread, you can either implement the Runnable interface or inherit from the Thread class.
- Runnable provides only one run() method to run a task, while Thread provides many other methods to control the flow, such as start(), interrupt(), join() and sleep().
- Runnable can be used in many cases, while Thread can only be used in some specific cases. Therefore, using Runnable is a better choice in case you want to run multiple threads with the same task.
Ok now let’s make a program and analyze it
1 2 3 4 5 6 7 8 9 | public class HanhGa extends Thread { public static void main(String[] args) { Thread t = Thread.currentThread(); System.out.println("The current Thread is :" + t); t.setName("HanhGa"); System.out.println("The thread is now named: " + t); } } |
Extract from the results
Each thread in a Java program is registered for a priority. The Java virtual machine never changes thread precedence. The priority remains constant until the thread is interrupted.
Each thread has a priority value that ranges from Thread.MIN_PRIORITY to Thread.MAX_PRIORITY ie 1-10. Each thread depends on a thread pool, and each thread pool has its own priority usually equal to 5.
Thread’s status
1 Thread in Java usually exists 6 states
New : Thread has been created but not run yet.
Runnable : Thread is running or waiting for CPU to be executed.
Blocked : Thread is blocked while waiting for an object to be used by another thread.
Waiting : Thread is waiting for an event to happen or another thread to send a notification to it.
Timed waiting : Thread is waiting for a certain amount of time.
Terminated : Thread has ended or was shut down carelessly.
When a thread is created, it will not run automatically, but needs to be called via the start() method.
When the sleep() method is called, it will be suspended and returned to the Waiting state
Some common functions of Thread.
getName() gets the Thread name.
isAlive() returns True if Thread still exists.
getPriority() returns Thread’s priority.
setName() sets the name for the Thread.
join() this function will wait until your Thread dies.
isDaemon() is Thread Daemon or not.
resume() marks the thread as a rare thread (Daemon).
sleep() suspends execution.
start() Calls the run() method to start a thread.
Rare Thread (Daemon Thread)
Daemon Thread aka the friendlier name and rare thread is designated as the “background” thread. Users create user threads and provide “services” to other threads.
In Java there is always at least one rare thread that is known as the “garbage collection” thread.
You can use the setDaemon method to designate a Thread as a Rare Thread.
Example Thread Notify, Simple Thread Pool
Let’s do a simple example of Thread.
In this example we will create a Thread Pool, and when the Thread completes we will fire notify to notify.
Ok! Let’s start….
In order for Thread to be able to register Listeners, I will create a new Thread that implements from Runnable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; public abstract class NotifyingThread implements Runnable { private final Set<ThreadCompleteListener> listeners = new CopyOnWriteArraySet<ThreadCompleteListener>(); public final void addListener(final ThreadCompleteListener listener) { listeners.add(listener); } public final void removeListener(final ThreadCompleteListener listener) { listeners.remove(listener); } private void notifyListeners() { for (ThreadCompleteListener listener : listeners) { listener.notifyOfThreadComplete(this); } } @Override public final void run() { try { doRun(); } finally { notifyListeners(); } } public abstract void doRun(); public abstract void startThread(); public abstract Thread getThread(); } |
Create the ThreadCompleteListener
interface for the Listeners to re-implement.
1 2 3 4 | public interface ThreadCompleteListener { void notifyOfThreadComplete(final Runnable thread); } |
Create a class to manage Thread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | public class ThreadStart extends NotifyingThread { Supplier<Object> supplier; VoidSupplier supplierVoid; String supplierMode = "object"; Thread t; public ThreadStart(Supplier<Object> func) { t = new Thread(this, func.getClass().getName() + new Random().nextInt()); supplier = func; supplierMode = "object"; } public ThreadStart(VoidSupplier func) { t = new Thread(this, func.getClass().getName() + new Random().nextInt()); supplierVoid = func; supplierMode = "void"; } @Override public void startThread(){ t.start(); } @Override public Thread getThread(){ return t; } @Override public void doRun() { try { if (supplierMode.equals("object")) supplier.apply(); else supplierVoid.apply(); } catch (Throwable e) { e.printStackTrace(); } } } |
- VoidSupplier is also a similar FunctionalInterface from Supplier.
Ok, let’s test it now.
1 2 3 4 | NotifyingThread thread = new ThreadStart(() -> new ClassRunThread().process()); thread.addListener(new ClassReceiveNotify()); thread.startThread(); |
where ClassRunThread
is the class they want to run in the new Thread, ClassReceiveNotify
implements ThreadCompleteListener to receive notifications.
and this is the result we get.
A little more advanced, let’s create a Pool for it to manage the amount of Thread created.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | public class ThreadPool implements ThreadCompleteListener { public ThreadPool(int maxThread) { ThreadPool.maxThread = maxThread; } public ThreadPool(int maxThread, ThreadCompleteListener... event) { ThreadPool.maxThread = maxThread; ThreadPool.event = event; } public void run(Supplier<Object> supplier) { try { while (ThreadPool.currentThread >= ThreadPool.maxThread){ Thread.sleep(100); } setCurrentThread(1); NotifyingThread thread1 = new ThreadStart(supplier); thread1.addListener(this); if (event != null) { for (var e : event){ thread1.addListener(e); } } thread1.startThread(); } catch (Throwable e) { e.printStackTrace(); } } public void run(VoidSupplier supplier) { try { while (ThreadPool.currentThread >= ThreadPool.maxThread){ Thread.sleep(300); } setCurrentThread(1); NotifyingThread thread1 = new ThreadStart(supplier); thread1.addListener(this); if (event != null) { for (var e : event){ thread1.addListener(e); } } thread1.startThread(); } catch (Throwable e) { e.printStackTrace(); } } protected static volatile int maxThread = 5; protected static volatile ThreadCompleteListener event[] = null; protected static volatile int currentThread = 0; private static synchronized int threadCount() { return maxThread; } private static synchronized int setCurrentThread(int t) { return currentThread = currentThread + t; } public static int getCurrentThread() { return currentThread; } public static int getMaxThread() { return maxThread; } @Override public void notifyOfThreadComplete(Runnable thread) { setCurrentThread(-1); } } |
In the above class I will save the number of Threads running in the currentThread, I consider the ThreadPool as a Listener to receive notifications when the Thread completes and adjust the currentThread.
In the above example, I use the sleep function to suspend 100ms when currentThread > maxThread. (There will be many other ways for you to do it, for example, put in Queue…. in this simple example, I will sleep quickly =)) )
Ok! Next, I will create another Builder for it to be professional
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class ThreadBuilder { int maxThread; ThreadCompleteListener event[]; public static ThreadBuilder newBuilder() { return new ThreadBuilder(); } public ThreadBuilder withMaxThread(int maxThread) { this.maxThread = maxThread; return this; } public ThreadBuilder withMaxThread(ThreadCompleteListener... event) { this.event = event; return this; } public ThreadPool build() { if (event == null) return new ThreadPool(maxThread); return new ThreadPool(maxThread, event); } } |
Now let’s see the results.
1 2 3 4 5 6 | ThreadPool run = ThreadBuilder.newBuilder().withMaxThread(2).withMaxThread(new ClassReceiveNotify()).build(); run.run(() -> new ClassRunThread().process(1)); run.run(() -> new ClassRunThread().process(2)); run.run(() -> new ClassRunThread().process(3)); run.run(() -> new ClassRunThread().process(4)); |
I created a ThreadPool with a max of 2, but I run 4 Threads at the same time and the picture below is the result
As you can see process 1
and process 2
are processed at the same time, while process 3
and process 4
need to wait because Pool size only allows 2.
Conclude
In this article, I have gone through the theory of Thread, made a simple example of Thread that I have drawn from real projects and simplified it for you to understand.
Thank you for watching the article, if there are any mistakes in the content, I hope to receive comments from everyone