1 source

  • Source: Java high Concurrency programming in detail multithreading and architecture design, by Wang Wenjun
  • Chapters: Chapters 1, 2 and 3

This article is the notes of the first three chapters.

2 an overview

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

3 Thread life cycle

3.1 Five Stages

The thread lifecycle can be divided into five phases:

  • NEW
  • RUNNABLE
  • RUNNING
  • BLOCKED
  • TERMINATED

3.2 NEW

When a Thread object is created with new, but the Thread is not started with start(), 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. You can use the start() method to enter the RUNNABLE state.

3.3 RUNNABLE

To enter the RUNNABLE state, you must call the start() method, which creates a thread in the JVM. However, once a thread is created, it cannot be executed immediately. Whether a thread executes or not depends on CPU scheduling. In other words, it is in an executable state and qualified for execution, but it is waiting to be scheduled instead of actually executing.

The RUNNABLE state can only end unexpectedly or enter the RUNNING state.

3.4 RUNNING

Once the CPU has selected a thread from the task executable queue by polling or other means, the thread can only be executed, that is, in the RUNNING state. In this state, the following state transitions may occur:

  • Enter theTERMINATED: For example, the call is no longer recommendedstop()methods
  • Enter theBLOCKED: For example, calledsleep()/wait()Method, or perform some blocking operation (get a lock resource, diskIOEtc.)
  • Enter theRUNNABLE:CPUThe time slice is up, or the thread is actively calledyield()

3.5 BLOCKED

This is the blocking state. There are many reasons for entering the blocking state, including the following:

  • diskIO
  • Network operating
  • To enter a blocking operation in order to acquire a lock

When BLOCKED, the following state transitions can occur:

  • Enter theTERMINATED: For example, calling an unrecommendedstop()Or,JVMAccidental death
  • Enter theRUNNABLE: Such as the end of hibernation, bynotify()/nofityAll()Wake up, acquire a lock, block the process byinterrupt()Interrupt etc.

3.6 TERMINATED

TERMINATED is the final state of a thread. Entry into TERMINATED state indicates the end of the thread’s life cycle, for example, in the following cases:

  • The thread ends properly
  • A thread running error ended unexpectedly
  • JVMUnexpected crash, forcing all threads to terminate

4 ThreadA constructor

4.1 Construction method

There are altogether eight constructors of Thread, which are classified according to their naming methods. The constructors using the default name are as follows:

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

A named thread is constructed 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 constructor:

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

In the default naming constructor, you can see in the source code that the default naming is actually the command for Thread-x (X is a number) :

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

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

private static synchronized int nextThreadNum(a) {
    return threadInitNumber++;
}
Copy the code

The name constructor is a custom name.

Also, if you want to change the name of a thread, you can call setName(), but note that only threads in the NEW state can change the name.

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);
Copy the code

Here’s a snippet of the source code:

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(); }}}Copy the code

You can see that there is currently a local variable called parent and it’s assigned to currentThread(), which is a native method. Since the initial state of a thread is NEW, currentThread() represents the thread that created its own thread, i.e., the following conclusion:

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

The parent thread is the main thread, which is created by the JVM.

In addition, several of the Thread constructors have the ThreadGroup parameter, which specifies which ThreadGroup the Thread is in. If a Thread is created without a specified ThreadGroup, it will have the same ThreadGroup as the parent Thread. The ThreadGroup of the main thread is called main.

4.3 aboutstackSize

The Thread constructor has a stackSize parameter that specifies the number of bytes in the address space that the JVM allocates to the Thread stack. It is platform dependent, on some platforms:

  • Set to a larger value: This can increase or decrease the recursion depth of in-thread callsStackOverflowErrorProbability of occurrence
  • Set a low value: To increase the number of threads created, it can be delayedOutOfMemoryErrorTime of occurrence

However, on some platforms this parameter does not work at all. Also, setting it to 0 won’t do anything.

5 Thread API

5.1 sleep()

Sleep () has two overloaded methods:

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

The sleep() method is wrapped in timeUnit.xxxx.sleep () instead of thread.sleep () :

TimeUnit.SECONDS.sleep(1);
TimeUnit.MINUTES.sleep(3);
Copy the code

5.2 yield()

Yield () is a heuristic that alerts the CPU scheduler to the voluntary abandonment of resources by the current thread and will be ignored if CPU resources are not strained. Calling yield() causes the current thread to change from RUNNING to RUNNABLE.

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

  • sleep()Causes the current thread to pause for the specified timeCPUTime slice consumption
  • yield()Just forCPUA hint from the scheduler ifCPUThe scheduler does not ignore this prompt, resulting in a thread context switch
  • sleep()Causes the thread to briefly block and be released within a given timeCPUresources
  • ifyield()To take effect,yield()Make fromRUNNINGState entryRUNNABLEstate
  • sleep()Will do almost 100% of the sleep for a given time, butyield()A hint is not necessarily a guarantee
  • One thread callsleep()And another thread callsinterrupt()Will pick up an interrupt signal, andyieldWill not be

5.3 setPriority()

5.3.1 Priorities

Threads, like processes, have their own priorities. In theory, a thread with a higher priority has a chance to be scheduled first, but in practice this is not the case. Setting a priority is similar to yield() and is also a reminder operation:

  • forrootThe user will alert the operating system to the desired priority level, otherwise it will be ignored
  • ifCPUIf you are busy, you may get more by setting prioritiesCPUTime slice, but when idle, the priority level has almost no effect

Therefore, setting a priority is to a large extent to allow a thread to get as many execution opportunities as possible, that is, to allow the thread itself to be scheduled by the operating system as much as possible, rather than setting a higher priority must run first, or a higher priority thread must run first than a lower priority thread.

5.3.2 Priority source code analysis

To set the priority, call setPriority().

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 newIllegalArgumentException(); }}Copy the code

It can be seen that the priority is between [1,10] and cannot be set higher than the priority of the current ThreadGroup. Finally, set the priority through the native method setPriority0.

Normally, the priority level of a thread is not set. By default, the priority of a thread is 5, because the main thread has a priority of 5 and main is the parent of all threads, so it also has a priority of 5 by default.

5.4 interrupt()

Interrupt () is an important API. There are three apis for thread interrupts:

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

The following will analyze them one by one.

5.4.1 interrupt()

Some method calls cause the current thread to block, such as:

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

Calling interrupt(), however, interrupts a block. Interrupting a block does not end the thread’s life cycle, but merely interrupts the blocking state of the current thread. Once interrupted while blocking, an InterruptedException is thrown, which acts as a signal to inform the current thread that it has been interrupted, as shown in the following example:

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();
}
Copy the code

Outputs a message that the thread was interrupted.

5.4.2 isInterrupted()

IsInterrupted () checks whether the current thread has been interrupted. This checks only for interrupt() and does not affect any changes to the interrupt() flag, as shown in the following example:

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());
}
Copy the code

The output is:

Thread is interrupted :false
Thread is interrupted :true
Copy the code

Here’s another example:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread() {
        @Override
        public void run(a) {
            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());
}
Copy the code

Output result:

Thread is interrupted :false
In catch block thread is interrupted :false
Thread is interrupted :false
Copy the code

If the Thread is not interrupted at first, the result is false. If the interrupt method is called and an exception is caught in the loop, the Thread itself will erase the interrupt identifier and reset it, so the output is false.

5.4.3 interrupted()

This is a static method. Calling this method erases the interrupt mark on the thread. Note that if the current thread is interrupted:

  • First callinterrupted()Returns thetrue“And wipe it off immediatelyinterruptlogo
  • The second and subsequent calls always returnfalseUnless 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(a) {
            while (true) { System.out.println(Thread.interrupted()); }}}; thread.setDaemon(true);
    thread.start();
    TimeUnit.MILLISECONDS.sleep(2);
    thread.interrupt();
}
Copy the code

Output (partial cut) :

false
false
false
true
false
false
false
Copy the code

If interrupted() is interrupted, the interrupt flag is immediately erased and true is returned for this only case.

The difference between interrupted() and isInterrupted() can be seen in the source code (OpenJDK 11) :

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

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

@HotSpotIntrinsicCandidate
private native boolean isInterrupted(boolean var1);
Copy the code

In fact, both call the same native method, where the Boolean variable indicates whether the thread’s interrupt identifier is erased:

  • trueI want to erase,interrupted()That’s how you do it
  • falseI don’t want to erase,isInterrupted()That’s how you do it

5.5 join()

5.5.1 join()Introduction to the

Join (), like sleep(), is a method that can interrupt. If other threads interrupt the current thread, they will catch the interrupt signal and erase the thread’s interrupt identifier. Join () provides three apis, as follows:

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

5.5.2 example

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(a){
        try{
            TimeUnit.MILLISECONDS.sleep(2);
        }catch(InterruptedException e){ e.printStackTrace(); }}}Copy the code

The output is captured as follows:

2 # 8
1 # 8
2 # 9
1 # 9
main # 0
main # 1
main # 2
main # 3
main # 4
Copy the code

Thread 1 and thread 2 execute alternately, while the main thread waits until thread 1 and thread 2 have finished executing.

6 Thread Closing

Thread has an outdated stop method that can be used to close a Thread, but the problem is that the monitor lock may not be released, so it is not recommended to close a Thread using this method. Thread closure can be divided into three categories:

  • Normally closed
  • Abnormal exit
  • Feign death

6.1 Normal Shutdown

6.1.1 Normal End

When a thread finishes running, it exits normally, which is the most common case.

6.1.2 Catch a signal to close the thread

To close a thread by catching an interrupt signal, as shown in the following example:

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
        @Override
        public void run(a) {
            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();
}
Copy the code

Always check to see if the interrupt flag is set to true, which breaks the loop. Another way is to use sleep() :

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
        @Override
        public void run(a) {
            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();
}
Copy the code

Also 6.1.3volatile

Since the interrupt identifier is likely to be erased or the interrupt() method will not be called, the alternative is to use volatile to modify a Boolean variable and iterate:

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

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

        public void close(a){
            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(); }}Copy the code

6.2 Abnormal Exit

Unchecked exceptions are not allowed to be thrown in thread execution units. If you need to catch checked exceptions while a thread is running and determine whether it is still necessary to run, encapsulate them as unchecked exceptions, such as RuntimeException. Throws to end the thread’s life cycle.

6.3 feign death

Suspended animation is a thread that exists but has no outward appearance, such as:

  • No log output
  • You don’t do any homework

Wait, even though the thread exists, it looks like it’s dead, but in fact it’s not. This is most likely because the thread is blocked, or two threads are competing for resources and there is a deadlock.

This situation requires external tools such as VisualVM, JConsole, etc., to identify the thread in question and its current state, and to determine which method is blocking.