Detailed explanation of multithreading based on Java review

  • 2020-04-01 01:53:52
  • OfStack

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

For the 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 created, one is garbage collection.

Thread basic information

We can use the thread.currentthread () method to get some information about the currentThread and modify it.

Let's look at the following code:


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

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

The properties listed are explained as follows:

      GroupName Each thread will be in a group of threads by default. We can also explicitly create a group of threads. A group of threads can also contain subgroups of threads.

      The Name , each Thread will have a name, and if not explicitly specified, the rule for the name is "thread-xxx."

      Priority , each thread has its own priority, which the JVM handles "preemptively." When the JVM finds a thread with a high priority, it runs the thread immediately. For multiple threads of equal priority, the JVM polls them. In Java, where Thread priorities range from 1 to 10, with a default of 5, the Thread class defines two constants: MIN_PRIORITY and MAX_PRIORITY to represent the highest and lowest priorities.

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


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

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

     thread1.setPriority(Thread.MIN_PRIORITY);
     thread2.setPriority(Thread.MAX_PRIORITY);
     thread1.start();
     thread2.start();
 }

      As you can see from the results of the run, the low-priority thread runs after the high-priority thread completes.
      isDaemon , this property is used to control the relationship between parent and child threads. If set to true, when the parent thread ends, all the child threads under it also end. On the contrary, the life cycle of the child thread is not affected by the parent thread.
Let's look at the following example:

IsDaemon  The sample 
 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++)
                     {
                         System.out.println("Sub Thread Running " + i);
                     }
                 }
             };
             subThread.setDaemon(true);
             subThread.start();
             System.out.println("Main Thread end.");
         }
     };

     thread1.start();
 }

      The above code results in and removes the subthread.setdaemon (true); In the latter case, it is found that the neutron thread will finish its execution and then end, while in the former case, the child thread will end soon.

How to create threads

All of the above is to demonstrate some information in the default thread, so how to create a thread? In Java, we have three ways to create threads.

Threads in Java either inherit from the Thread class or implement the Runnable interface.

Use inner classes to create threads

We can use an inner class to create threads by declaring a variable of type Thread and rewriting the run method. The sample code is as follows:


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

Thread is inherited to create threads

We can derive a class from Thread and override its run method in a similar way. The sample code is as follows:


 The derived Thread Class to create threads 
 class MyThread extends Thread
 {
     public void run()
     {
         for (int i =0; i < 5; i++)
         {
             System.out.println("Thread " + Thread.currentThread().getName() + " is running.");
         }
         System.out.println("Thread " + Thread.currentThread().getName() + " is finished.");
     }
 }

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

Implement the Runnable interface to create threads

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


 implementation Runnable Interface to create threads 
 class MyRunnable implements Runnable
 {
     public void run() 
     {
         for (int i =0; i < 5; i++)
         {
             System.out.println("Thread " + Thread.currentThread().getName() + " is running.");
         }
         System.out.println("Thread " + Thread.currentThread().getName() + " is finished.");
     }
 }

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

Threads can be created in all three ways, and from the example code, threads perform the same function, so what's the difference between the three ways?

This refers to the multithreaded running mode in Java. For Java, there is a difference between "multi-object multi-threading" and "single-object multi-threading" at runtime:

      Multiobject multithreading , the program creates multiple thread objects while running, with one thread running on each object.
      Single object multithreading , the program creates a thread object while it is running and runs multiple threads on it.

Obviously, multi-object multithreading is simpler from a thread synchronization and scheduling perspective. The first two are "multi-object multi-threading", and the third can use either "multi-object multi-threading" or "single-object single-thread".

Let's look at the following sample code, which USES the object.notify method, which wakes up a thread on the Object; The object.notifyall method, on the other hand, wakes up all the threads on the Object.


notify The sample 
 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 < arrThreads.length; i++)
         {
             arrThreads[i] = new MyThread();
             arrThreads[i].id = i;
             arrThreads[i].setDaemon(true);
             arrThreads[i].start();
         }
         Thread.sleep(500);
         for (int i = 0; i < arrThreads.length; 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 < arrThreads.length; i++)
         {
             arrMyRunners[i] = new MyRunner();
             arrMyRunners[i].id = i;
             arrThreads[i] = new Thread(arrMyRunners[i]);
             arrThreads[i].setDaemon(true);
             arrThreads[i].start();
         }
         Thread.sleep(500);
         for (int i = 0; i < arrMyRunners.length; 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 < arrThreads.length; i++)
         {
             arrThreads[i] = new Thread(runner);
             arrThreads[i].setDaemon(true);
             arrThreads[i].start();
         }
         Thread.sleep(500);

         synchronized(runner)
         {
             runner.notifyAll();
         }
     }
 }

 class MyThread extends Thread
 {
     public int id = 0;
     public void run()
     {
         System.out.println(" The first " + id + " Three threads are ready to sleep 5 Minutes. ");
         try
         {
             synchronized(this)
             {
                 this.wait(5*60*1000);
             }
         }
         catch(InterruptedException ex)
         {
             ex.printStackTrace();
         }
         System.out.println(" The first " + id + " Four threads are awakened. ");
     }
 }

 class MyRunner implements Runnable
 {
     public int id = 0;
     public void run() 
     {
         System.out.println(" The first " + id + " Three threads are ready to sleep 5 Minutes. ");
         try
         {
             synchronized(this)
             {
                 this.wait(5*60*1000);
             }
         }
         catch(InterruptedException ex)
         {
             ex.printStackTrace();
         }
         System.out.println(" The first " + id + " Four threads are awakened. ");
     }

 }

In the sample code, notifyTest() and notifyTest2() are "multi-object multithreading," and while the threads in notifyTest2() implement the Runnable interface, it defines an array of threads with each element using a new instance of Runnable. NotifyTest3 () is "single-object multithreading" because we only define one instance of Runnable, which all threads will use.

The notifyAll method is suitable for "single-object multithreaded" scenarios, because the notify method wakes up only one thread on the object at random.

State switching of threads

For a thread, from the time we create it to the end of the thread's run, the thread's state might look like this:

      Create: you already have a Thread instance, but the CPU still allocates resources and time slices for it.
      Ready: the thread has all the resources it needs to run and is just waiting for the CPU to schedule it.
      Run: the thread is in the current CPU slice and executing the logic.
      Sleep: this is typically the state after calling thread.sleep, where the Thread still holds all the resources it needs to run, but is not scheduled by the CPU.
      Suspend: this is typically the state after calling thread.suspend. Like hibernation, the CPU does not schedule the Thread, except that in this state, the Thread releases all resources.
      Dead: the Thread runs out or the thread.stop method is called.

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

      Thread() or Thread(Runnable) : constructs threads.
      Thread.start: start a Thread.
      Thread.sleep: switches a Thread to sleep.
      Thread.interrupt: to interrupt the execution of a Thread.
      Thread.join: waits for a Thread to end.
      Thread.yield: strips a Thread of its execution time on the CPU, waiting for the next schedule.
      Object.wait: locks all threads on Object until the notify method continues to run.
      Object. Notify: randomly wakes up 1 thread on the Object.
      NotifyAll: wakes up all threads on Object.

It's time for the demo!!

Thread wait and wake

The main methods used here are object.wait and object.notify, see the notify instance above. It is important to note that both wait and notify must be for the same object, and when we create threads in a way that implements the Runnable interface, we should use both methods on the Runnable object instead of the Thread object.

Thread sleep and wake


Thread.sleep The instance 
 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()
             {
                 System.out.println(" thread  " + Thread.currentThread().getName() + " Going to sleep 5 Minutes. ");
                 try
                 {
                     Thread.sleep(5*60*1000);
                 }
                 catch(InterruptedException ex)
                 {
                     System.out.println(" thread  " + Thread.currentThread().getName() + " Sleep is interrupted. ");
                 }
                 System.out.println(" thread  " + Thread.currentThread().getName() + " Hibernation ends. ");
             }
         };
         thread.setDaemon(true);
         thread.start();
         Thread.sleep(500);
         thread.interrupt();
     }

 }

While the Thread is asleep, we can wake it up with thread.interrupt, at which point the Thread throws InterruptedException.

Thread termination

While there is a thread.stop method, which is not recommended, we can take advantage of the sleep and wake mechanism above to let the Thread terminate when it handles an IterruptedException.


Thread.interrupt The sample 
 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()
             {
                 System.out.println(" Thread running. ");
                 try
                 {
                     Thread.sleep(1*60*1000);
                 }
                 catch(InterruptedException ex)
                 {
                     System.out.println(" Thread interrupt, end the thread ");
                     return;
                 }
                 System.out.println(" The thread ends normally. ");
             }
         };
         thread.start();
         Thread.sleep(500);
         thread.interrupt();
     }
 }

Synchronous waiting of threads

Thread.join comes in when we create 10 child threads in the main Thread, and then we expect the main Thread to execute the following logic when all 10 child threads are finished.


Thread.join The sample 
 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++)
                     {
                         System.out.println(" The thread is running. ");
                         Thread.sleep(1000);
                     }
                 }
                 catch(InterruptedException ex)
                 {
                     ex.printStackTrace();
                 }
             }
         };
         thread.setDaemon(true);
         thread.start();
         Thread.sleep(1000);
         thread.join();
         System.out.println(" The main thread ends normally. ");
     }
 }

We can try thread.join(); Comment or delete, run the program again, and you'll see the difference.

Interthread communication

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

The following two examples do exactly the same thing, except that one USES Stream and the other Reader/Writer.


PipeInputStream/PipedOutpueStream  The sample 
 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(System.in));
             try
             {
                 while(true)
                 {
                     String message = br.readLine();
                     pos.write(message.getBytes());
                     if (message.equals("end")) break;
                 }
                 br.close();
                 pos.close();
             }
             catch(Exception ex)
             {
                 ex.printStackTrace();
             }
         }
     };

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

     thread1.setDaemon(true);
     thread2.setDaemon(true);
     thread1.start();
     thread2.start();
     thread1.join();
     thread2.join();
 }


PipedReader/PipedWriter  The sample 
 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(System.in));
             try
             {
                 while(true)
                 {
                     String message = br.readLine();
                     bw.write(message);
                     bw.newLine();
                     bw.flush();
                     if (message.equals("end")) break;
                 }
                 br.close();
                 pw.close();
                 bw.close();
             }
             catch(Exception ex)
             {
                 ex.printStackTrace();
             }
         }
     };

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

             String line = null;
             try
             {
                 while((line = br.readLine()) != null)
                 {
                     System.out.println(line);
                     if (line.equals("end")) break;
                 }
                 br.close();
                 pr.close();
             }
             catch(Exception ex)
             {
                 ex.printStackTrace();
             }
         }
     };

     thread1.setDaemon(true);
     thread2.setDaemon(true);
     thread1.start();
     thread2.start();
     thread1.join();
     thread2.join();
 }


Related articles: