Home >Java >javaTutorial >Detailed graphic explanation of life cycle and state control in Java threads

Detailed graphic explanation of life cycle and state control in Java threads

黄舟
黄舟Original
2017-05-21 10:25:212155browse

This article mainly introduces the life cycle and status control of Java threads. Friends in need can refer to the following

1. Thread life cycle

Thread state transition diagram:

Detailed graphic explanation of life cycle and state control in Java threads

1. New state

After using the new keyword and the Thread class or its subclass to create a thread object , the thread object is in a new state. The thread in the new state has its own memory space and enters the ready state (runnable) by calling the start method.

Note: The start() method cannot be called again on an already started thread, otherwise a Javalang.IllegalThreadStateException exception will occur.

2. Ready state

#The thread in the ready state has the running conditions, but has not yet been allocated to the CPU and is in the thread readyQueue (Although it is in the form of a queue, in fact, it is called a runnable pool rather than a runnable queue. Because the scheduling of the CPU is not necessarily scheduled in a first-in, first-out order), the waiting system is It allocates CPU. The waiting state is not the execution state. When the system selects a Thread object waiting for execution, it will enter the execution state from the waiting execution state. The action selected by the system is called "cpu scheduling". Once it obtains the CPU, the thread enters the running state and automatically calls its own run method.

Tip: If you want the sub-thread to execute immediately after calling the start() method, you can use the Thread.sleep() method to make the main thread sleep together and then execute the sub-thread.

3. Running state

The thread in the running state is the most complex. It can become blocked, ready and dead. If a thread in the ready state is scheduled by the CPU, it will change from the ready state to the running state and execute the tasks in the run() method. If the thread loses CPU resources, it will change from the running state to the ready state. Wait for the system to allocate resources again. You can also call the yield() method on a thread in the running state, and it will give up CPU resources and become ready again.

When the following situations occur, the thread will change from the running state to the blocking state:

①. The thread calls the sleep method to actively give up the occupied system resources

②. The thread calls a blocking IO method. Before the method returns, the thread is blocked.

③. The thread tries to obtain a synchronization monitor, but the changed synchronization monitor is being held by other threads. Yes

④. The thread is waiting for a notification (notify)

⑤. The program calls the suspend method of the thread to end the thread. hang. However, this method can easily lead to deadlock, so programs should try to avoid using this method.

When the thread's run() method is executed, or is forcibly terminated, for example, an exception occurs, or the stop(), desireory() method, etc. are called, it will Transition from running state to dead state.

4. Blocking state

#The thread in the running state under certain circumstances, such as executing the sleep (sleep) method, or waiting Resources such as I/O devices will give up the CPU and temporarily stop their own operation and enter a blocking state.
Threads in the blocked state cannot enter the ready queue. Only when the cause of the blocking is eliminated, such as the sleep time has expired, or the waiting I/O device is idle, the thread will enter the ready state, queue up in the ready queue again, and start from the original stop position after being selected by the system. Keep running.

5. Death state

When the thread's run() method is executed or is forcibly terminated, it is considered dead. This thread object may be alive, but it is no longer a separately executed thread. Once a thread dies, it cannot be revived. If the start() method is called on a dead thread, a java.lang.IllegalThreadStateException exception will be thrown.

2. Thread state control

Java provides some convenient methods for controlling thread state.

Detailed graphic explanation of life cycle and state control in Java threads.

You can see that many methods have been marked as obsolete. We should avoid using them as much as possible, but should focus on start(), interrupt(), join(), Direct control methods such as sleep() and yield(), and indirect control methods such as setDaemon() and setPriority().

1. Thread sleep——sleep

If we need to let the currently executing thread pause for a period of time and enter a blocking state, then You can call Thread's sleep method. From the above, you can see that the sleep method has two overloaded forms, but the usage method is the same.

For example, we want to make the main thread sleep for 100 milliseconds every time, and then print out the numbers:


##

 public class Test { 
  public static void main(String[] args) throws InterruptedException { 
   for(int i=;i<;i++){ 
    System.out.println("main"+i); 
    Thread.sleep(); 
   } 
  } 
 }

You can clearly see that the printed numbers are in time There is a slight gap on the top.

Pay attention to the following issues

①. sleep is a

static method. It is best not to call it with an instance object of Thread because it sleeps is always the currently running thread, not the thread object that calls it. It is only valid for the thread object in the running state. Look at the following example:


public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   System.out.println(Thread.currentThread().getName()); 
   MyThread myThread=new MyThread(); 
   myThread.start(); 
   myThread.sleep(1000);//这里sleep的就是main线程,而非myThread线程 
   Thread.sleep(10); 
   for(int i=0;i<100;i++){ 
    System.out.println("main"+i); 
   } 
  } 
 }

②. Java thread scheduling is the core of Java multi-threading. Only good scheduling can give full play to the performance of the system and improve the execution efficiency of the program. . However, no matter how the programmer writes the schedule, it can only affect the order of thread execution to the maximum extent, but cannot achieve precise control. Because after using the sleep method, the thread enters the blocking state. Only when the sleep time is over, will it re-enter the ready state. The transition from the ready state to the running state is controlled by the system. We cannot interfere with it accurately. , so if Thread.sleep(1000) is called to cause the thread to sleep for 1 second, the result may be greater than 1 second.


public class Test1 { 
public static void main(String[] args) throws InterruptedException { 
   new MyThread().start(); 
new MyThread().start(); 
  } 
 }  
 class MyThread extends Thread { 
  @Override 
  public void run() { 
   for (int i = 0; i < 3; i++) { 
    System.out.println(this.getName()+"线程" + i + "次执行!"); 
    try { 
     Thread.sleep(50); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 
   } 
  } 
 }

Look at the results of a certain operation:

1. Thread-0 thread is executed 0 times!

2. Thread-1 thread is executed 0 times!
3. Thread-1 thread is executed once!
4. Thread-0 thread is executed once!
5. Thread-0 thread is executed twice!
6. Thread-1 thread is executed twice!

As you can see, thread 0 is executed first, then thread 1 is executed once, and then again. You can see that it is not executed in the order of sleep.

2. Thread yield - yield

The yield() method is somewhat similar to the sleep() method, and it is also one provided by the Thread class Static method, it can also pause the currently executing thread and give up CPU resources to other threads. But unlike the sleep() method, it does not enter the blocking state, but enters the ready state. The yield() method just pauses the current thread, re-enters the ready thread pool, and lets the system's thread scheduler reschedule it again. It is entirely possible that this situation will occur: when a thread calls the yield() method, The thread scheduler schedules it out and re-enters the running state for execution.

In fact, when a thread calls the yield() method to pause,

The priority is the same as the current thread, or the thread in the ready state with a higher priority than the current thread has more It is possible to obtain the opportunity to execute, of course, it is only possible, because we cannot accurately interfere with the CPU scheduling thread.

Usage of yield:


public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   new MyThread("低级", 1).start(); 
   new MyThread("中级", 5).start(); 
   new MyThread("高级", 10).start(); 
  } 
 } 
 class MyThread extends Thread { 
  public MyThread(String name, int pro) { 
   super(name);// 设置线程的名称 
   this.setPriority(pro);// 设置优先级 
  } 
  @Override 
  public void run() { 
   for (int i = 0; i < 30; i++) { 
    System.out.println(this.getName() + "线程第" + i + "次执行!"); 
   if (i % 5 == 0) 
     Thread.yield(); 
   } 
  } 
 }

The difference between sleep() method and yield() method is as follows:

①. After the sleep method suspends the current thread, it will enter the blocking state. Only when the sleep time is up, will it enter the ready state. After the yield method is called, it directly enters the ready state, so it may be scheduled to the running state just after entering the ready state.

②. The sleep method declares that InterruptedException is thrown, so when calling the sleep method, you must catch the exception, or explicitly declare that the exception is thrown. The yield method does not declare a task exception.

③. The sleep method has better portability than the yield method. Generally, do not rely on the yield method to control the execution of concurrent threads.

3. Thread merging - join

The meaning of thread merging is to merge several parallel threads into a single thread for execution. The application scenario is when a thread must wait for another thread to finish executing before it can execute. The Thread class provides the join method to complete this function. Note that it is not a static method.

As you can see from the list of methods above, it has 3 overloaded methods:

void join()

Current thread, etc. It's time to join the thread and wait for the thread to terminate.


void join(long millis)   

    当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度  

void join(long millis,int nanos)   

    等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度 

例子:


 public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   MyThread thread=new MyThread(); 
   thread.start(); 
   thread.join(1);//将主线程加入到子线程后面,不过如果子线程在1毫秒时间内没执行完,则主线程便不再等待它执行完,进入就绪状态,等待cpu调度 
   for(int i=0;i<30;i++){ 
    System.out.println(Thread.currentThread().getName() + "线程第" + i + "次执行!"); 
   } 
  } 
 } 
 class MyThread extends Thread { 
  @Override 
 public void run() { 
   for (int i = 0; i < 1000; i++) { 
    System.out.println(this.getName() + "线程第" + i + "次执行!"); 
   } 
  } 
 }

在这个例子中,在主线程中调用thread.join(); 就是将主线程加入到thread子线程后面等待执行。不过有时间限制,为1毫秒。

4、线程的优先级

每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级。

Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,范围是1~•0之间,也可以使用Thread类提供的三个静态常量

  • MAX_PRIORITY   =10

  • MIN_PRIORITY   =1

  • NORM_PRIORITY   =5

例子:


public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   new MyThread("高级", 10).start(); 
   new MyThread("低级", 1).start(); 
  } 
 }  
 class MyThread extends Thread { 
  public MyThread(String name,int pro) { 
   super(name);//设置线程的名称 
   setPriority(pro);//设置线程的优先级 
  } 
  @Override 
  public void run() { 
   for (int i = 0; i < 100; i++) { 
    System.out.println(this.getName() + "线程第" + i + "次执行!"); 
   } 
  } 
 }

从结果可以看到 ,一般情况下,高级线程更显执行完毕。

注意一点:虽然Java提供了10个优先级别,但这些优先级别需要操作系统的支持。不同的操作系统的优先级并不相同,而且也不能很好的和Java的10个优先级别对应。所以我们应该使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设定优先级,这样才能保证程序最好的可移植性。

5、守护线程

守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。 

守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。 

setDaemon方法的详细说明:

public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。  

该方法必须在启动线程前调用。 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。  

  参数:

    on - 如果为 true,则将该线程标记为守护线程。       

  抛出:   

    IllegalThreadStateException - 如果该线程处于活动状态。     

    SecurityException - 如果当前线程无法修改该线程。


 /** 
 * Java线程:线程的调度-守护线程 
 */ 
 public class Test { 
   public static void main(String[] args) { 
     Thread t1 = new MyCommon(); 
     Thread t2 = new Thread(new MyDaemon()); 
     t2.setDaemon(true);  //设置为守护线程 
     t2.start(); 
     t1.start(); 
   } 
 } 
 class MyCommon extends Thread { 
   public void run() { 
     for (int i = 0; i < 5; i++) { 
       System.out.println("线程1第" + i + "次执行!"); 
       try { 
         Thread.sleep(7); 
       } catch (InterruptedException e) { 
         e.printStackTrace(); 
       } 
     } 
   } 
 }  
 class MyDaemon implements Runnable { 
   public void run() { 
     for (long i = 0; i < 9999999L; i++) { 
      System.out.println("后台线程第" + i + "次执行!"); 
       try { 
         Thread.sleep(7); 
       } catch (InterruptedException e) { 
         e.printStackTrace(); 
       } 
     } 
   } 
 }

执行结果:

1. 后台线程第0次执行! 
2. 线程1第0次执行! 
3. 线程1第1次执行! 
4. 后台线程第1次执行! 
5. 后台线程第2次执行! 
6. 线程1第2次执行! 
7. 线程1第3次执行! 
8. 后台线程第3次执行! 
9. 线程1第4次执行! 
10. 后台线程第4次执行! 
11. 后台线程第5次执行! 
12. 后台线程第6次执行! 
13. 后台线程第7次执行!

从上面的执行结果可以看出:前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。 

实际上:JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台县城时候一定要注意这个问题。

守护线程的用途:

守护线程通常用于执行一些后台作业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里做自动语法检查、自动保存等功能。Java的垃圾回收也是一个守护线程。守护线的好处就是你不需要关心它的结束问题。例如你在你的应用程序运行的时候希望播放背景音乐,如果将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候,不仅要退出主线程,还要通知播放背景音乐的线程退出;如果设定为守护线程则不需要了。

6、如何结束一个线程

Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit这些终止线程运行的方法已经被废弃了,使用它们是极端不安全的!想要安全有效的结束一个线程,可以使用下面的方法。

1、正常执行完run方法,然后结束掉

2、控制循环条件和判断条件的标识符来结束掉线程

比如说run方法这样写:


 class MyThread extends Thread { 
  int i=0; 
  @Override 
  public void run() { 
   while (true) { 
    if(i==10) 
     break; 
    i++; 
    System.out.println(i); 
    
   } 
  } 
 }

或者


 class MyThread extends Thread { 
  int i=0; 
  boolean next=true; 
  @Override 
  public void run() { 
   while (next) { 
    if(i==10) 
     next=false; 
    i++; 
    System.out.println(i); 
   } 
  } 
 }

或者


 class MyThread extends Thread { 
  int i=0; 
  @Override 
  public void run() { 
   while (true) { 
    if(i==10) 
     return; 
    i++; 
    System.out.println(i); 
   } 
  } 
 }

只要保证在一定的情况下,run方法能够执行完毕即可。而不是while(true)的无线循环。

3、使用interrupt结束一个线程。

诚然,使用第2中方法的标识符来结束一个线程,是一个不错的方法,但是如果,该线程是处于sleep、wait、join的状态的时候,while循环就不会执行,那么我们的标识符就无用武之地了,当然也不能再通过它来结束处于这3种状态的线程了。

可以使用interrupt这个巧妙的方式结束掉这个线程。

我们看看sleep、wait、join方法的声明:


 public final void wait() throws InterruptedException 
 public static native void sleep(long millis) throws InterruptedException 
 public final void join() throws InterruptedException

可以看到,这三者有一个共同点,都抛出了一个InterruptedException的异常。

在什么时候会产生这样一个异常呢?

每个Thread都有一个中断状状态,默认为false。可以通过Thread对象的isInterrupted()方法来判断该线程的中断状态。可以通过Thread对象的interrupt()方法将中断状态设置为true。

当一个线程处于sleep、wait、join这三种状态之一的时候,如果此时他的中断状态为true,那么它就会抛出一个InterruptedException的异常,并将中断状态重新设置为false。

看下面的简单的例子:


 public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   MyThread thread=new MyThread(); 
   thread.start(); 
  } 
 }  
 class MyThread extends Thread { 
  int i=1; 
  @Override 
  public void run() { 
   while (true) { 
    System.out.println(i); 
   System.out.println(this.isInterrupted()); 
    try { 
     System.out.println("我马上去sleep了"); 
    Thread.sleep(2000); 
     this.interrupt(); 
    } catch (InterruptedException e) { 
     System.out.println("异常捕获了"+this.isInterrupted()); 
     return; 
    } 
    i++; 
   } 
  } 
 }

测试结果:

1. 1 
2. false 
3. 我马上去sleep了 
4. 2 
5. true 
6. 我马上去sleep了 
7. 异常捕获了false 

可以看到,首先执行第一次while循环,在第一次循环中,睡眠2秒,然后将中断状态设置为true。当进入到第二次循环的时候,中断状态就是第一次设置的true,当它再次进入sleep的时候,马上就抛出了InterruptedException异常,然后被我们捕获了。然后中断状态又被重新自动设置为false了(从最后一条输出可以看出来)。

所以,我们可以使用interrupt方法结束一个线程。具体使用如下:


 public class Test1 { 
  public static void main(String[] args) throws InterruptedException { 
   MyThread thread=new MyThread(); 
   thread.start(); 
   Thread.sleep(3000); 
   thread.interrupt(); 
 } 
 } 
 
 class MyThread extends Thread { 
  int i=0; 
  @Override 
  public void run() { 
   while (true) { 
   System.out.println(i); 
    try { 
     Thread.sleep(1000); 
    } catch (InterruptedException e) { 
    System.out.println("中断异常被捕获了"); 
     return; 
    } 
    i++; 
  } 
  } 
 }

多测试几次,会发现一般有两种执行结果:

 0 
1  
 2 

4. 中断异常被捕获了

或者

 0  
 1 


5. 中断异常被捕获了 

这两种结果恰恰说明了  只要一个线程的中断状态一旦为true,只要它进入sleep等状态,或者处于sleep状态,立马回抛出InterruptedException异常。

第一种情况,是当主线程从3秒睡眠状态醒来之后,调用了子线程的interrupt方法,此时子线程正处于sleep状态,立马抛出InterruptedException异常。

The first situation is when the main thread wakes up from the 3-second sleep state and calls the interrupt method of the sub-thread. At this time, the sub-thread is not in the sleep state. Then during the third while loop, it enters the sleep state and immediately throws an InterruptedException.

The above is the detailed content of Detailed graphic explanation of life cycle and state control in Java threads. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn