5

1 source

  • Source: "Detailed Multithreading and Architecture Design of Java High Concurrency Programming", written by Wang Wenjun
  • Chapters: Chapters 1, 2, and 3

This article is a compilation of notes from the first three chapters.

2 Overview

This article mainly describes the life cycle of the thread, the construction method of the Thread class and the commonly used API , and finally introduces the closing method of the thread.

3 Thread life cycle

3.1 The five stages

The thread life cycle can be divided into five phases:

  • NEW
  • RUNNABLE
  • RUNNING
  • BLOCKED
  • TERMINATED

3.2 NEW

When a Thread object is created with new , the thread is not started with start() , and the thread is in the NEW state. To be precise, it's just the state of the Thread object, which is a normal Java object. At this point, you can enter the RUNNABLE state through the start() method.

3.3 RUNNABLE

To enter the RUNNABLE state, the start() method must be called, thus creating a thread in JVM . However, once a thread is created, it cannot be executed immediately. Whether the thread is executed or not needs to be scheduled by CPU , that is to say, it is in an executable state at this time and qualified for execution, but it is not actually executed, but is Waiting to be scheduled.

RUNNABLE state can only terminate unexpectedly or enter the RUNNING state.

3.4 RUNNING

Once CPU selects a thread from the task executable queue by polling or other means, the thread can be executed at this time, that is, it is in the RUNNING state. In this state, the possible state transitions are as follows:

  • Enter TERMINATED : For example, call the deprecated stop() method
  • Enter BLOCKED : For example, the sleep() / wait() method is called, or a blocking operation is performed (acquisition of lock resources, disk IO , etc.)
  • Enter RUNNABLE : CPU time slice arrives, or the thread actively calls yield()

3.5 BLOCKED

That is, the blocking state, there are many reasons for entering the blocking state, the common ones are as follows:

  • Disk IO
  • network operation
  • Entering a blocking operation to acquire a lock

When in state BLOCKED , possible state transitions are as follows:

  • Enter TERMINATED : such as calling the deprecated stop() , or JVM unexpectedly died
  • Enter RUNNABLE : such as the end of hibernation, being woken up by notify() / nofityAll() , acquiring a lock, interrupting the blocking process by interrupt() , etc.

3.6 TERMINATED

TERMINATED is the final state of the thread. After entering this state, it means that the life cycle of the thread ends. For example, it will enter this state in the following cases:

  • Thread run ends normally
  • Thread running error ended unexpectedly
  • JVM Unexpected crash causing all threads to forcibly end

4 Thread construction method

4.1 Constructor

There are eight construction methods of Thread , which are classified according to the naming method. The construction methods using the default naming are as follows:

  • Thread()
  • Thread(Runnable target)
  • Thread(ThreadGroup group,Runnable target)

The constructor for named threads is as follows:

  • Thread(String name)
  • Thread(Runnable target,Strintg name)
  • Thread(ThreadGroup group,String name)
  • Thread(ThreadGroup group,Runnable target,String name)
  • Thread(ThreadGroup group,Runnable target,String name,long stackSize)

But in fact all constructors end up calling the following private constructors:

private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);

In the default naming construction method, you can see in the source code that the default naming is actually the command of Thread-X (X is a number):

public Thread() {
    this((ThreadGroup)null, (Runnable)null, "Thread-" + nextThreadNum(), 0L);
}

public Thread(Runnable target) {
    this((ThreadGroup)null, target, "Thread-" + nextThreadNum(), 0L);
}

private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

And in the named constructor is the custom name.

In addition, if you want to modify the name of the thread, you can call the setName() method, but it should be noted that the thread in the NEW state can only be modified.

4.2 Parent-child relationship of threads

All constructors of Thread call the following methods:

private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);

One of the source code snippets is as follows:

if (name == null) {
    throw new NullPointerException("name cannot be null");
} else {
    this.name = name;
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        if (security != null) {
            g = security.getThreadGroup();
        }

        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
}

You can see that there is currently a local variable called parent , and the assignment is currentThread() , and currentThread() is a native method. Because the initial state when a thread is created is NEW , so currentThread() represents the thread that created its own thread, that is, the conclusion is as follows:

  • The creation of one thread must be done by another thread
  • The parent thread of the created thread is the thread that created it

That is, the thread created by itself, the parent thread is the main thread, and the main thread is created by JVM .

In addition, several of the construction methods of Thread have the ThreadGroup parameter, which specifies which ThreadGroup the thread is located in. If a thread is created without specifying ThreadGroup , it will be the same ThreadGroup as the parent thread. main where the ThreadGroup thread resides is called main .

4.3 About stackSize

There is a stackSize parameter in the Thread construction method, which specifies the number of bytes in the address space of the thread stack allocated by JVM , which is highly dependent on the platform. On some platforms:

  • Setting a larger value: It can increase the recursion depth of calls within the thread and reduce the probability of StackOverflowError appearing
  • Setting a lower value: it can increase the number of threads created and delay the appearance of OutOfMemoryError

However, on some platforms this parameter has no effect. Also, if set to 0 it will not have any effect.

5 Thread API

5.1 sleep()

sleep() has two overloaded methods:

  • sleep(long mills)
  • sleep(long mills,int nanos)

But after JDK1.5 , TimeUnit was introduced, which provides a good encapsulation for the sleep() method. It is recommended to use TimeUnit.XXXX.sleep() instead of Thread.sleep() :

TimeUnit.SECONDS.sleep(1);
TimeUnit.MINUTES.sleep(3);

5.2 yield()

yield() is a heuristic method that reminds CPU scheduler that the current thread will voluntarily give up resources. If CPU resource is not tight, this reminder will be ignored. Calling the yield() method will change the current thread from RUNNING to RUNNABLE state.

Regarding the difference between yield() and sleep() , the differences are as follows:

  • sleep() will cause the current thread to suspend for the specified time, without the consumption of the CPU time slice
  • yield() is just a hint to the CPU scheduler. If the CPU scheduler does not ignore this hint, it will cause a thread context switch
  • sleep() will cause the thread to block briefly, releasing CPU resources within a given time
  • If yield() takes effect, yield() will make the state from RUNNING enter the state of RUNNABLE
  • sleep() will almost 100% complete sleep for a given time, but yield() 's prompt may not guarantee
  • One thread calling sleep() and another thread calling interrupt() will catch the interrupt signal, but yield will not

5.3 setPriority()

5.3.1 Introduction to Priority

Similar to a process, a thread also has its own priority. In theory, a thread with a higher priority will have the opportunity to be scheduled first, but this is not the case. Setting the priority is similar to yield() , and it is also a reminder operation:

  • For root users, the operating system will be reminded of the priority you want to set, otherwise it will be ignored
  • If CPU is busy, setting the priority may get more CPU time slices, but the priority level when idle has almost no effect

Therefore, setting the priority is only to allow a thread to get as many execution opportunities as possible to a large extent, that is, to let the thread itself be scheduled by the operating system as much as possible, instead of setting a high priority, it must run first, or priority Threads with a higher priority must run in preference to threads with a lower priority.

5.3.2 Priority source code analysis

Set the priority and directly call setPriority() . The source code of OpenJDK 11 is as follows:

public final void setPriority(int newPriority) {
    this.checkAccess();
    if (newPriority <= 10 && newPriority >= 1) {
        ThreadGroup g;
        if ((g = this.getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }

            this.setPriority0(this.priority = newPriority);
        }

    } else {
        throw new IllegalArgumentException();
    }
}

It can be seen that the priority is between [1,10] and cannot be set to a priority greater than the current ThreadGroup . Finally, the priority is set by native method setPriority0 .

Under normal circumstances, the priority level of the thread is not set. By default, the priority of the thread is 5, because the priority of the main thread is 5, and main is the parent process of all threads, so the priority of the thread is by default. Level is also 5.

5.4 interrupt()

interrupt() is an important API , and there are three API thread interruptions as follows:

  • void interrupt()
  • boolean isInterrupted()
  • static boolean interrupted()

The following will analyze them one by one.

5.4.1 interrupt()

Some method calls will cause the current thread to enter a blocking state, such as:

  • Object.wait()
  • Thread.sleep()
  • Thread.join()
  • Selector.wakeup()

Calling interrupt() can interrupt the blocking. Interrupting the blocking does not mean the end of the thread's life cycle, but only interrupts the blocking state of the current thread. Once interrupted in the blocking state, an exception of InterruptedException will be thrown. This exception is like a signal to notify the current thread that the interrupt has been interrupted. The example is as follows:

public static void main(String[] args) throws InterruptedException{
    Thread thread = new Thread(()->{
        try{
            TimeUnit.SECONDS.sleep(10);
        }catch (InterruptedException e){
            System.out.println("Thread is interrupted.");
        }
    });
    thread.start();
    TimeUnit.SECONDS.sleep(1);
    thread.interrupt();
}

Will output the information that the thread was interrupted.

5.4.2 isInterrupted()

isInterrupted() can judge whether the current thread is interrupted, it is only a judgment of the interrupt() logo, and will not affect any changes in the logo (because when interrupt flag interrupt() be set). The example is as follows:

public static void main(String[] args) throws InterruptedException{
    Thread thread = new Thread(()->{
        while (true){}
    });
    thread.start();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("Thread is interrupted :"+thread.isInterrupted());
    thread.interrupt();
    System.out.println("Thread is interrupted :"+thread.isInterrupted());
}

The output is:

Thread is interrupted :false
Thread is interrupted :true

Another example is as follows:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread() {
        @Override
        public void run() {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    System.out.println("In catch block thread is interrupted :" + isInterrupted());
                }
            }
        }
    };
    thread.start();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("Thread is interrupted :" + thread.isInterrupted());
    thread.interrupt();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("Thread is interrupted :" + thread.isInterrupted());
}

Output result:

Thread is interrupted :false
In catch block thread is interrupted :false
Thread is interrupted :false

At the beginning, the thread was not interrupted, and the result was false . After calling the interrupt method, an exception (signal) was caught in the loop body. At this time, Thread itself will erase interrupt flag and reset the flag, so the output result after the exception is caught is also false .

5.4.3 interrupted()

This is a static method. Calling this method will erase the thread's interrupt identifier. It should be noted that if the current thread is interrupted:

  • The first call to interrupted() will return true and immediately erase interrupt flag
  • The second and subsequent calls will always return false unless the thread is interrupted again in the meantime

Examples are as follows:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread() {
        @Override
        public void run() {
            while (true) {
                System.out.println(Thread.interrupted());
            }
        }
    };
    thread.setDaemon(true);
    thread.start();
    TimeUnit.MILLISECONDS.sleep(2);
    thread.interrupt();
}

Output (truncated part):

false
false
false
true
false
false
false

It can be seen that there is a true , that is, interrupted() judges that it is interrupted, and the interrupt flag will be erased immediately, and only this time it returns true , followed by false .

About the difference between interrupted() and isInterrupted() , you can know from the source code ( OpenJDK 11 ):

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

public boolean isInterrupted() {
    return this.isInterrupted(false);
}

@HotSpotIntrinsicCandidate
private native boolean isInterrupted(boolean var1);

In fact, both call the same native method, where the boolean variable indicates whether to erase the thread's interrupt identity:

  • true means you want to erase, interrupted() does it
  • false means you don't want to erase, isInterrupted() does it

5.5 join()

5.5.1 Introduction join()

join() , like sleep() , is an interruptible method. If other threads execute the interrupt operation on the current thread, the interrupt signal will also be captured, and the interrupt identifier of the thread will be join() API

  • void join()
  • void join(long millis,int nanos)
  • void join(long mills)

5.5.2 Examples

A simple example is as follows:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> threads = IntStream.range(1,3).mapToObj(Main::create).collect(Collectors.toList());
        threads.forEach(Thread::start);
        for (Thread thread:threads){
            thread.join();
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+" # "+i);
            shortSleep();
        }
    }

    private static Thread create(int seq){
        return new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+" # "+i);
                shortSleep();
            }
        },String.valueOf(seq));
    }

    private static void shortSleep(){
        try{
            TimeUnit.MILLISECONDS.sleep(2);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

The output is intercepted as follows:

2 # 8
1 # 8
2 # 9
1 # 9
main # 0
main # 1
main # 2
main # 3
main # 4

Thread 1 and thread 2 execute alternately, and the main thread will wait until thread 1 and thread 2 are executed before executing.

6 threads close

There is an obsolete method Thread in stop , which can be used to close a thread, but the problem is that the lock of monitor may not be released, so it is not recommended to use this method to close a thread. Thread shutdown can be divided into three categories:

  • graceful shutdown
  • abnormal exit
  • suspended animation

6.1 Graceful shutdown

6.1.1 Normal end

After the thread finishes running, it will exit normally, which is the most common situation.

6.1.2 Catch the signal to close the thread

To close the thread by catching the interrupt signal, the example is as follows:

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
        @Override
        public void run() {
            System.out.println("work...");
            while(!isInterrupted()){

            }
            System.out.println("exit...");
        }
    };
    t.start();
    TimeUnit.SECONDS.sleep(5);
    System.out.println("System will be shutdown.");
    t.interrupt();
}

Always check whether the interrupt flag is set to true , and set it to true to jump out of the loop. Another way is to use sleep() :

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
        @Override
        public void run() {
            System.out.println("work...");
            while(true){
                try{
                    TimeUnit.MILLISECONDS.sleep(1);
                }catch (InterruptedException e){
                    break;
                }
            }
            System.out.println("exit...");
        }
    };
    t.start();
    TimeUnit.SECONDS.sleep(5);
    System.out.println("System will be shutdown.");
    t.interrupt();
}

6.1.3 volatile

Since the interrupt identifier is likely to be erased, or interrupt() method will not be called, another method is to use volatile decorate a boolean variable and continue to loop to judge:

public class Main {
    static class MyTask extends Thread{
        private volatile boolean closed = false;

        @Override
        public void run() {
            System.out.println("work...");
            while (!closed && !isInterrupted()){

            }
            System.out.println("exit...");
        }

        public void close(){
            this.closed = true;
            this.interrupt();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyTask t = new MyTask();
        t.start();
        TimeUnit.SECONDS.sleep(5);
        System.out.println("System will be shutdown.");
        t.close();
    }
}

6.2 Abnormal exit

The checked exception is not allowed to be thrown in the thread execution unit. If the checked exception needs to be caught during the thread running process and it is necessary to judge whether it is still necessary to run, the checked exception can be encapsulated as the unchecked exception, such as RuntimeException , and thrown to end the thread life cycle.

6.3 Suspended Death

The so-called suspended animation means that although the thread exists, it does not have any external manifestation, such as:

  • no log output
  • do not do any work

Wait, although the thread exists at this time, it looks like it is dead. In fact, it is not dead. In this case, it is most likely because the thread is blocked, or two threads are competing for resources and died. Lock.

This situation needs to be judged by some external tools, such as VisualVM , jconsole , etc., to find out the thread in question and the current state, and determine which method caused the blocking.


氷泠
420 声望647 粉丝