SoFunction
Updated on 2025-03-08

Detailed explanation of multi-threading based on Java review

Threads are the basic unit of operating system operation. They are encapsulated in a process, and a process can contain multiple threads. Even if we do not create threads manually, the process will have a default thread running.

For JVM, when we write a single-threaded program to run, there are at least two threads running in the JVM, one is the program we create, and the other is garbage collection.

Basic thread information

We can obtain some information about the current thread through the() method and modify it.

Let's look at the following code:

Copy the codeThe code is as follows:

View and modify the properties of the current thread
 String name = ().getName();
         int priority = ().getPriority();
         String groupName = ().getThreadGroup().getName();
         boolean isDaemon = ().isDaemon();
         ("Thread Name:" + name);
         ("Priority:" + priority);
         ("Group Name:" + groupName);
         ("IsDaemon:" + isDaemon);

         ().setName("Test");
         ().setPriority(Thread.MAX_PRIORITY);
         name = ().getName();
         priority = ().getPriority();
         groupName = ().getThreadGroup().getName();
         isDaemon = ().isDaemon();
         ("Thread Name:" + name);
         ("Priority:" + priority);

The properties listed are as follows:

    GroupName, Each thread will be in a thread group by default. We can also explicitly create thread groups, and a thread group can also contain child thread groups, so that threads and thread groups form a tree structure.

    Name, Each thread will have a name. If not specified explicitly, the rule of the name is "Thread-xxx".

    Priority, Each thread will have its own priority, and the JVM handles priority in a "preemptive way". When the JVM discovers a thread with high priority, it will run the thread immediately; for multiple threads with equal priority, the JVM polls it. Java's thread priority ranges from 1 to 10, and the default is 5. The Thread class defines 2 constants: MIN_PRIORITY and MAX_PRIORITY to represent the highest and lowest priority.

We can look at the following code, which defines two threads with different priorities:

Copy the codeThe code is as follows:

Thread priority example
 public static void priorityTest()
 {
     Thread thread1 = new Thread("low")
     {
         public void run()
         {
             for (int i = 0; i < 5; i++)
             {
                 ("Thread 1 is running.");
             }
         }
     };

     Thread thread2 = new Thread("high")
     {
         public void run()
         {
             for (int i = 0; i < 5; i++)
             {
                 ("Thread 2 is running.");
             }
         }
     };

     (Thread.MIN_PRIORITY);
     (Thread.MAX_PRIORITY);
     ();
     ();
 }

From the running results, it can be seen that the low-priority thread only runs after the high-priority thread is completed.
    isDaemon, This property is used to control the relationship between parent and child threads. If set to true, when the parent thread ends, all child threads below it will also end. Otherwise, the life cycle of the child thread is not affected by the parent thread.
Let's look at the following example:
Copy the codeThe code is as follows:

IsDaemon Example
 public static void daemonTest()
 {
     Thread thread1 = new Thread("daemon")
     {
         public void run()
         {
             Thread subThread = new Thread("sub")
             {
                 public void run()
                 {
                     for(int i = 0; i < 100; i++)
                     {
                         ("Sub Thread Running " + i);
                     }
                 }
             };
             (true);
             ();
             ("Main Thread end.");
         }
     };

     ();
 }

Comparing the running results of the above code after deletion (true);, we can find that the child thread will complete execution and then end in the latter process, while in the former, the child thread will end soon.

How to create a thread

The above content is all about some information in the default thread. So how should we create a thread? In Java, we have 3 ways to create threads.

Threads in Java either inherit the Thread class or implement the Runnable interface. Let's talk about it one by one.

Use internal classes to create threads

We can use the inner class to create threads, the process is to declare a variable of type Thread and override the run method. The sample code is as follows:

Copy the codeThe code is as follows:

Create threads using internal classes
 public static void createThreadByNestClass()
 {
     Thread thread = new Thread()
     {
         public void run()
         {
             for (int i =0; i < 5; i++)
             {
                 ("Thread " + ().getName() + " is running.");
             }
             ("Thread " + ().getName() + " is finished.");
         }
     };
     ();
 }

Inherit Thread to create threads

We can derive a class from Thread and override its run method, which is similar to the above. The sample code is as follows:

Copy the codeThe code is as follows:

Deriving Thread class to create threads
 class MyThread extends Thread
 {
     public void run()
     {
         for (int i =0; i < 5; i++)
         {
             ("Thread " + ().getName() + " is running.");
         }
         ("Thread " + ().getName() + " is finished.");
     }
 }

 
 public static void createThreadBySubClass()
 {
     MyThread thread = new MyThread();
     ();
 }

Implement the Runnable interface to create threads

We can define a class that implements the Runnable interface, and then use an instance of the class as a parameter to build the Thread variable constructor. The sample code is as follows:

Copy the codeThe code is as follows:

Implement the Runnable interface to create threads
 class MyRunnable implements Runnable
 {
     public void run()
     {
         for (int i =0; i < 5; i++)
         {
             ("Thread " + ().getName() + " is running.");
         }
         ("Thread " + ().getName() + " is finished.");
     }
 }

 
 public static void createThreadByRunnable()
 {
     MyRunnable runnable = new MyRunnable();
     Thread thread = new Thread(runnable);
     ();
 }

The above three methods can create threads, and from the example code, the functions of thread execution are the same, so what are the differences between these three methods of creation?

This involves the running mode of multi-threading in Java. For Java, when multi-threading is running, there are differences between "multi-object multi-threading" and "single-object multi-threading":

    Multi-object multi-threading, The program creates multiple thread objects during running, and one thread is run on each object.
    Single object multithreading, The program creates a thread object during running, and runs multiple threads on it.

Obviously, from the perspective of thread synchronization and scheduling, multi-object and multi-threading is simpler. The first two methods of thread creation are "multi-object multi-threading", and the third type can use "multi-object multi-threading" or "single-object single-threading".

Let's look at the following example code, which will use a method, which will wake up a thread on the object; and a method will wake up all threads on the object.

Copy the codeThe code is as follows:

notify example
 public class NotifySample {

     public static void main(String[] args) throws InterruptedException
     {
         notifyTest();
         notifyTest2();
         notifyTest3();
     }

     private static void notifyTest() throws InterruptedException
     {
         MyThread[] arrThreads = new MyThread[3];
         for (int i = 0; i < ; i++)
         {
             arrThreads[i] = new MyThread();
             arrThreads[i].id = i;
             arrThreads[i].setDaemon(true);
             arrThreads[i].start();
         }
         (500);
         for (int i = 0; i < ; i++)
         {
             synchronized(arrThreads[i])
             {
                 arrThreads[i].notify();
             }
         }
     }

     private static void notifyTest2() throws InterruptedException
     {
         MyRunner[] arrMyRunners = new MyRunner[3];
         Thread[] arrThreads = new Thread[3];
         for (int i = 0; i < ; i++)
         {
             arrMyRunners[i] = new MyRunner();
             arrMyRunners[i].id = i;
             arrThreads[i] = new Thread(arrMyRunners[i]);
             arrThreads[i].setDaemon(true);
             arrThreads[i].start();
         }
         (500);
         for (int i = 0; i < ; i++)
         {
             synchronized(arrMyRunners[i])
             {
                 arrMyRunners[i].notify();
             }
         }
     }

     private static void notifyTest3() throws InterruptedException
     {
         MyRunner runner = new MyRunner();
         Thread[] arrThreads = new Thread[3];
         for (int i = 0; i < ; i++)
         {
             arrThreads[i] = new Thread(runner);
             arrThreads[i].setDaemon(true);
             arrThreads[i].start();
         }
         (500);

         synchronized(runner)
         {
             ();
         }
     }
 }

 class MyThread extends Thread
 {
     public int id = 0;
     public void run()
     {
("Threads" + id + "Threads are ready to sleep for 5 minutes.");
         try
         {
             synchronized(this)
             {
                 (5*60*1000);
             }
         }
         catch(InterruptedException ex)
         {
             ();
         }
("Threads" + id + "Threads are awakened.");
     }
 }

 class MyRunner implements Runnable
 {
     public int id = 0;
     public void run()
     {
("Threads" + id + "Threads are ready to sleep for 5 minutes.");
         try
         {
             synchronized(this)
             {
                 (5*60*1000);
             }
         }
         catch(InterruptedException ex)
         {
             ();
         }
("Threads" + id + "Threads are awakened.");
     }

 }

In the example code, notifyTest() and notifyTest2() are "multi-object multi-threading". Although the threads in notifyTest2() implement the Runnable interface, when defining the Thread array, each element uses a new Runnable instance. notifyTest3() belongs to "single object multithreading", because we only define one Runnable instance, and all threads will use this instance.

The notifyAll method is suitable for "single object multithreading" scenarios, because the notify method will only wake up one thread on the object randomly.

Thread state switching

For threads, from the time we create it until the thread runs, the state of the thread may be like this:

Create: There is already a Thread instance, but the CPU also allocates resources and time slices for it.
Ready: The thread has obtained all the resources needed to run and is waiting for the CPU to schedule it.
Run: The thread is located in the current CPU time slice and is executing related logic.
Hibernation: Generally, it is in the state after the call. At this time, the thread still holds various resources required for operation, but it will not be scheduled by the CPU.
Suspend: Generally, it is in the state after the call, similar to sleep, the CPU will not schedule the thread. The difference is that in this state, the thread will release all resources.
Death: The thread ends or the method is called.

Let's demonstrate how to switch thread state. First, we will use the following method:

Thread() or Thread(Runnable): Construct threads.
: Start the thread.
: Switch the thread to sleep.
: interrupt the execution of the thread.
: Wait for a certain thread to end.
: Deprive threads of execution time slice on the CPU and wait for the next schedule.
: Lock all threads on the Object and do not continue to run until the notify method.
: Randomly wake up 1 thread on the Object.
: Wake up all threads on the Object.

Below is the demonstration time! ! !

Thread waiting and wake up

For the main usage and methods here, please refer to the notify instance above. It should be noted that wait and notify must be targeted at the same object. When we use the method of implementing the Runnable interface to create threads, we should use these two methods on the Runnable object rather than the Thread object.

Hibernation and wake-up of threads

Copy the codeThe code is as follows:

Example
 public class SleepSample {

     public static void main(String[] args) throws InterruptedException
     {
         sleepTest();
     }

     private static void sleepTest() throws InterruptedException
     {
         Thread thread = new Thread()
         {
             public void run()
             {
("Thread" + ().getName() + "It will sleep for 5 minutes.");
                 try
                 {
                     (5*60*1000);
                 }
                 catch(InterruptedException ex)
                 {
("Thread " + ().getName() + "Hibern is interrupted.");
                 }
("Thread " + ().getName() + "Sleep end.");
             }
         };
         (true);
         ();
         (500);
         ();
     }

 }

During the hibernation process, we can use it to wake it up, and the thread will throw an InterruptedException.

Thread termination

Although there is a method, this method is not recommended. We can use the above hibernation and wake-up mechanisms to allow the thread to end the thread when processing IterruptedException.

Copy the codeThe code is as follows:

Example
 public class StopThreadSample {

     public static void main(String[] args) throws InterruptedException
     {
         stopTest();
     }

     private static void stopTest() throws InterruptedException
     {
         Thread thread = new Thread()
         {
             public void run()
             {
("Thread is running.");
                 try
                 {
                     (1*60*1000);
                 }
                 catch(InterruptedException ex)
                 {
("Thread interrupt, end thread");
                     return;
                 }
("Thread ends normally.");
             }
         };
         ();
         (500);
         ();
     }
 }

Thread synchronization waiting

When we create 10 child threads in the main thread and then we expect that after all the 10 child threads are finished, the main thread is executing the next logic, and then it is time to start.

Copy the codeThe code is as follows:

Example
 public class JoinSample {

     public static void main(String[] args) throws InterruptedException
     {
         joinTest();
     }

     private static void joinTest() throws InterruptedException
     {
         Thread thread = new Thread()
         {
             public void run()
             {
                 try
                 {
                     for(int i = 0; i < 5; i++)
                     {
("Thread is running.");
                         (1000);
                     }
                 }
                 catch(InterruptedException ex)
                 {
                     ();
                 }
             }
         };
         (true);
         ();
         (1000);
         ();
("The main thread ends normally.");
     }
 }

We can try to comment or delete (); and run the program again to find the difference.

Inter-thread communication

We know that all threads under a process share memory space, so how do we pass messages between different threads? When reviewing Java I/O, we talked about PipedStream and PipedReader, here is where they come into play.

The following two examples have exactly the same function, the difference is that one uses Stream and the other uses Reader/Writer.

Copy the codeThe code is as follows:

PipeInputStream/PipedOutpueStream Example
 public static void communicationTest() throws IOException, InterruptedException
 {
     final PipedOutputStream pos = new PipedOutputStream();
     final PipedInputStream pis = new PipedInputStream(pos);

     Thread thread1 = new Thread()
     {
         public void run()
         {
             BufferedReader br = new BufferedReader(new InputStreamReader());
             try
             {
                 while(true)
                 {
                     String message = ();
                     (());
                     if (("end")) break;
                 }
                 ();
                 ();
             }
             catch(Exception ex)
             {
                 ();
             }
         }
     };

     Thread thread2 = new Thread()
     {
         public void run()
         {
             byte[] buffer = new byte[1024];
             int bytesRead = 0;
             try
             {
                 while((bytesRead = (buffer, 0, )) != -1)
                 {
                     (new String(buffer));
                     if (new String(buffer).equals("end")) break;
                     buffer = null;
                     buffer = new byte[1024];
                 }
                 ();
                 buffer = null;
             }
             catch(Exception ex)
             {
                 ();
             }
         }
     };

     (true);
     (true);
     ();
     ();
     ();
     ();
 }

Copy the codeThe code is as follows:

PipedReader/PipedWriter Example
 private static void communicationTest2() throws InterruptedException, IOException
 {
     final PipedWriter pw = new PipedWriter();
     final PipedReader pr = new PipedReader(pw);
     final BufferedWriter bw = new BufferedWriter(pw);
     final BufferedReader br = new BufferedReader(pr);

     Thread thread1 = new Thread()
     {
         public void run()
         {

             BufferedReader br = new BufferedReader(new InputStreamReader());
             try
             {
                 while(true)
                 {
                     String message = ();
                     (message);
                     ();
                     ();
                     if (("end")) break;
                 }
                 ();
                 ();
                 ();
             }
             catch(Exception ex)
             {
                 ();
             }
         }
     };

     Thread thread2 = new Thread()
     {
         public void run()
         {

             String line = null;
             try
             {
                 while((line = ()) != null)
                 {
                     (line);
                     if (("end")) break;
                 }
                 ();
                 ();
             }
             catch(Exception ex)
             {
                 ();
             }
         }
     };

     (true);
     (true);
     ();
     ();
     ();
     ();
 }