Multithreading - thread life cycle and state control

1. Thread life cycle

 The life cycle of a thread is the process from creation to death of a thread. Regarding the life cycle of threads in Java, first take a look at the more classic picture below:

0b06a75702744b73afcf8a9584a284f7.png

When a thread is created and started, it neither enters the execution state as soon as it is started, nor does it remain in the execution state. In the life cycle of a thread , it goes through five different states : New , Ready , Runnable , Running , Blocked and Dead . Especially when a thread is started, it cannot always "occupy" the CPU to run alone, so the CPU needs to switch between multiple threads, so the thread status will switch between running and blocking multiple times.

1) New state ( New )

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

2) Ready state ( Runnable )

The thread in the ready state already has the running conditions (that is, it is qualified to run on the CPU), but has not yet been assigned the execution right of the CPU. It is in the "thread ready queue", waiting for the system to allocate a CPU to it. The ready state is not the execution state. When the system selects a Thread object waiting for execution, it will enter the execution state. Once it gets the CPU, the thread enters the running state and automatically calls its own run method.

3) Running status ( Running )

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.

The running state can change to blocking state, ready state and death state.

If the thread loses CPU resources, it will change from the running state to the ready state and 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.

4) Blocked state ( Blocked )

In some special circumstances, when someone suspends or performs input and output operations, it gives up the execution rights of the CPU and temporarily interrupts its own execution, thus entering a blocking state. It will not have the opportunity to be called by the CPU again until it enters the ready state. to enter the running state.

According to the different reasons for blocking, blocking status can be divided into three types:

1) Waiting for blocking: The thread in the running state executes the wait() method, causing this thread to enter the waiting blocking state.

           When methods such as notify() or notifyAll() are called, the thread will re-enter the ready state.

2) Synchronous blocking: If the thread fails to acquire the synchronization lock (because the lock is occupied by other threads), it will enter the synchronization blocking state.

When the synchronization lock is acquired successfully, the thread will return to the ready state.

3) Other blocking: When the thread sleep() or join() is called or an I/O request is issued, the thread will enter the blocking state.

           When the sleep() state times out, join() waits for the thread to terminate or times out, or the I/O processing is completed, the thread will re-enter the ready state.

5) Dead state ( Dead )

When the thread finishes executing the run() method or exits the run() method due to an exception, the thread ends its life cycle. In addition, if the thread executes the interrupt() or stop() method, it will also enter the death state by exiting abnormally.

2. Control of thread status

Methods provided in Java to control thread status:

Direct control methods: start(), interrupt(), join(), sleep(), yield()

Indirect control methods: setDaemon(), setPriority()

2.1 Thread sleep

If we need to make the currently executing thread pause for a period of time and enter the blocking state. After the specified time, the blocking state is unblocked and enters the ready state, we can call the sleep method of Thread. From the API, we can see that there are two sleep methods. Overloaded form, but used exactly the same way.

21e7a119b96e44c1b6831fd3afa4d633.png

For example, we want to make the main thread sleep every 1000 milliseconds and then print out the number:

[Example] Print a number every 1000 milliseconds

public class Test {

    public static void main(String[] args) {

        for(int i = 1; i < 10; i++) {

            System.out.println(i);

            try {

                Thread.sleep ( 1000 ); // Sleep the main thread for 1 second

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

}

By executing the code, we can clearly see that the printed numbers are slightly spaced in time.

Precautions:

        1) sleep is a static method. It is best not to call it with an instance object of Thread, because it always sleeps the currently running thread, not the thread object that calls it. It is only valid for the thread object in the running state.

        2) 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, and we cannot interfere accurately. It, so if Thread.sleep(1000) is called to cause the thread to sleep for 1 second, the result may be greater than 1 second.

2.2 Thread priority

Each thread has a priority attribute when executing. Threads with high priority can get more execution opportunities, while threads with low priority can get fewer execution opportunities. Similar to thread sleep, the priority of the thread still cannot guarantee the execution order of the thread. However, threads with high priority have a greater probability of obtaining CPU resources, and threads with low priority do not have no chance to execute.

The Thread class provides setPriority(int newPriority) and getPriority() methods to set and return the priority of a specified thread. The parameter of the setPriority method is an integer, which can be set to an integer between [1-10]. The larger the value, Then the higher the priority, you can also use the three static constants provided by the Thread class:

public final static int MIN_PRIORITY = 1;

public final static int NORM_PRIORITY = 5;

public final static int MAX_PRIORITY = 10;

[Example] Test the execution of thread priority

/**

 * Custom thread class

 */

class TestThread extends Thread {

    public TestThread() {}

    public TestThread(String name, int pro) {

        super (name); //Set the thread name

        this .setPriority(pro); // Set the priority of the thread

    }

    @Override

    public void run() {

        for (int i = 0; i < 10; i++) {

            System. out .println( this .getName() + "thread" + i + "thread execution!");

        }

    }

}

/**

 * Test class

 */

public class Test {

    public static void main(String[] args) {

        new TestThread("Advanced", 10).start();

        new TestThread("low-level", 1).start();

    }

}

Executing the program, you can see from the results that under normal circumstances, the high-level thread completes execution first.

2.3 Thread concession

yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也可以让当前正在执行的线程暂停,让出CPU资源给其它的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度一次,完全可能出现这样的情况:当某个线程调用yield()方法之后,线程调度器又将其调度出来重新进入到运行状态执行。

实际上,当某个线程调用了yield()方法暂停之后,优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程更有可能获得执行的机会,当然,只是有可能,因为我们不可能精确的干涉CPU调度线程。

【示例】线程让步的使用

/**

 * 自定义线程类

 */

class TestThread extends Thread {

    public TestThread() {}

    public TestThread(String name, int pro) {

        super(name); // 设置线程名字

        this.setPriority(pro); // 设置线程的优先级

    }

    @Override

    public void run() {

        for (int i = 0; i < 5; i++) {

            System.out.println(this.getName() + "线程第" + i + "次执行!");

            Thread.yield(); // 线程让步

        }

    }

}

/**

 * 测试类

 */

public class Test {

    public static void main(String[] args) {

        new TestThread("高级", 10).start();

        new TestThread("低级", 1).start();

    }

}

关于sleep()方法和yield()方的区别如下:

  1. sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后,是直接进入就绪状态,所以有可能刚进入就绪状态,又被调度到运行状态。
  2. sleep方法声明抛出了InterruptedException,所以调用sleep方法的时候要捕获该异常,或者显示声明抛出该异常。而yield方法则没有声明抛出任务异常。
  1. sleep方法比yield方法有更好的可移植性,通常不要依靠yield方法来控制并发线程的执行。

2.4 线程合并

线程的合并就是:线程A在运行期间,可以调用线程B的join()方法,这样线程A就必须等待线程B执行完毕后,才能继续执行。

应用场景是当一个线程必须等待另一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能,注意,它不是静态方法。

线程合并有三个重载的方法:

12871d6b948a401aa82fd398f9ffa871.png

【示例】线程合并的使用

/**

 * 自定义线程类

 */

class TestThread extends Thread {

    public TestThread() {}

    public TestThread(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 + "次执行!");

        }

    }

}

/**

 * 测试类

 */

public class Test {

    public static void main(String[] args) {

        TestThread th = new TestThread();

        th.start();

        try {

            // 等待th线程执行任务完毕之后,再执行主线程中的任务

            th.join();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        // 主线程任务

        for (int i = 0; i < 30; i++) {

            String name = Thread.currentThread().getName();

            System.out.println(name + "线程第" + i + "次执行!");

        }

    }

}

在这个例子中,在主线程中调用th.join(); 就是将主线程加入到th子线程后面等待执行。

2.5 守护线程

守护线程与普通线程写法上基本没啥区别,调用线程对象的方法setDaemon(true),就可以把该线程标记为守护线程。

当普通线程(前台线程)都全部执行完毕,也就是当前在运行的线程都是守护线程时,Java虚拟机(JVM)将退出。另外,setDaemon(true)方法必须在启动线程前调用,否则抛出IllegalThreadStateException异常。

【示例】线程合并的使用

// 前台线程

class CommonThread extends Thread {

    @Override

    public void run() {

        for (int i = 0; i < 5; i++) {

            System.out.println("前台线程第" + i + "次执行!");

        }

    }

}

// 后台线程或守护线程

class DaemonThread extends Thread {

    int i = 0;

    @Override

    public void run() {

        while(true) {

            System.out.println("后台线程第" + i++ + "次执行!");

        }

    }

}

//测试类

public class Test {

    public static void main(String[] args) {

        CommonThread ct = new CommonThread();

        DaemonThread dt = new DaemonThread();

        ct.start();

        dt.setDaemon( true ); // Set dt as a daemon thread

        dt.start();

    }

}

Run the above code, and you can see from the execution results: the foreground thread is guaranteed to complete execution, and the background thread exits before it completes execution.

The purpose of daemon thread:

Daemon threads are usually used to perform some background tasks, such as playing background music when your application is running, performing automatic grammar checking, automatic saving, etc. in a text editor. Java's garbage collection is also a daemon thread. The advantage of guarding the line is that you don't need to worry about its ending . For example, if you want to play background music while your application is running, if the thread that plays background music is set as a non-daemon thread, then when the user requests to exit, not only must the main thread exit, but also the background music must be notified to play. The thread exits; if it is set as a daemon thread, it is not needed.

 

 

Guess you like

Origin blog.csdn.net/shengshanlaolin_/article/details/127476522