Thread creation, interruption and communication

This article has been included in Github: github.com/JavaLiuTong…. For more dry goods articles, pay attention to the official account: Classmate Liu who can’t speak

Three ways to create threads

Inherit Thread

We want to simply start a thread in the code. The most familiar way is to inherit Thread. This class is a thread class provided by JDK. This class implements the Runnable interface. Let’s take a look at some of the more commonly used methods of Thread

The first is its construction method

image.png

For the construction method, we only need to focus on the first two, one is the no-argument construction, and the other is the implementation class passed in Runnable

Other more commonly used methods:

public synchronized void start() : start the thread

public void run(): The method specifically executed by the thread, which needs to be rewritten in the inherited class

public static native void sleep(long millis): The thread sleeps, and the sleep time needs to be passed in. This method will suspend the thread execution until the thread sleep time ends

public static void sleep(long millis, int nanos): Same as the previous method, except that an additional time mechanism is passed

public void interrupt(): Mark the thread with an interrupt flag

public boolean isInterrupted(): Determine whether the thread is interrupted

public final synchronized void setName(String name): Set a name for the thread

public final void join(): Let the main thread wait for the execution of the child thread to end

public static native void yield(): Indicates that the current thread is willing to give up CPU usage rights

I will not explain the specific use of the method here one by one, let’s take a look at the implementation details of the code

Sample code:

// custom class inherits Thread
public class Thread01 extends Thread{<!-- -->
    @Override
    public void run() {<!-- -->
       System.out.println("Hello World");
    }
}
public static void main(String[] args) throws Exception {<!-- -->
    Thread01 thread01 = new Thread01();
    // start the thread
    thread01.start();
  }

Implement the Runnable interface

Runnable internally provides a run abstract method. I also said above that Thread implements the Runnable interface

image.png

Because Runnable does not provide us with a method to start a thread, we need to use the Thread class to start it

The code example is as follows:

// Custom class implements Runnable interface
public class Thread02 implements Runnable {<!-- -->
    @Override
    public void run() {<!-- -->
        System.out.println("Hello World");
    }
}
public static void main(String[] args) throws Exception {<!-- -->
    Thread02 thread02 = new Thread02();
    // start with Thread class
    Thread thread = new Thread(thread02);
    // start the thread
    thread. start();
}

Implement the Callable interface

The above two ways to create threads have a disadvantage, but the results of thread execution cannot be obtained. We can obtain the results of thread execution by implementing the Callable interface

Similarly, Callable only provides one method, and the interface receives a generic type as the returned result parameter

image.png

Callable also does not provide a method to start a thread, so we also need to use the Thread class to start

But we can see that Thread does not provide a construction method for Callable parameter passing in addition to the construction method of Runnable parameter passing, so we cannot start it directly through Thread, we need a medium, this medium is FutureTask, in fact, we can find out carefully , Callable and FutureTask are both under the JUC concurrency package

FutureTask inherits the Runnable interface, so FutureTask itself is an implementation of Runnable

image.png

FutureTask also provides two construction methods, one is Callable parameter passing, the other is Runnable parameter passing

image.png

So we can wrap the implementation of Callable into a FutureTask, and then start it through Thread

Sample code:

// Custom Callable implementation
public class Thread03 implements Callable<String> {<!-- -->
    @Override
    public String call() throws Exception {<!-- -->
        return "This is the result of a thread execution...";
    }
}
public static void main(String[] args) throws Exception {<!-- -->
    Thread03 thread03 = new Thread03();
    // wrapped into FutureTask
    FutureTask futureTask=new FutureTask(thread03);
    // start with Thread class
    Thread thread = new Thread(futureTask);
    thread. start();
    
    // Get the execution result
    System.out.println(futureTask.get());
}

Thread lifecycle

Just like Spring’s beans, threads also have a life cycle

Java’s thread life cycle has six states:

NEW (initialization state)

RUNNABLE

BLOCKED (blocked state)

WAITING (no time limit waiting)

TIMED_WAITING (timed_waiting)

TERMINATED

Among them, the three states of BLOCKED, WAITING, and TIMED_WAITING can be attributed to one state, that is, the dormant state, because as long as these three states are one of them, the thread will never have the right to use the CPU.

Let’s take a look at how to transition between thread states

1. From NEW to RUNNABLE state

The Thread object just created by Java is in the NEW state. At this time, the JVM allocates memory for it and initializes the value of its member variables. The thread will not execute the thread execution body.

When the start() method is called, it will transition from NEW to RUNNABLE state

2. RUNNABLE to BLOCKED state

When a thread calls a synchronized modified method or code block, other threads can only wait, and the waiting thread will transition from RUNNABLE to BLOCKED state

When the waiting thread acquires the synchronized implicit lock, it will transition from BLOCKED to RUNNABLE state

3. From RUNNABLE to WAITING state

There are three scenarios that trigger transitions between these two states

In the first scenario, the thread that acquires the synchronized implicit lock calls the Object.wait() method without parameters

In the second scenario, call the Thread.join() method without parameters. If there is a thread ThreadA, when ThreadA.join() is called, the thread that executes this statement will wait for ThreadA to finish executing, and during the waiting process , the state will switch from RUNNABLE to WAITING, and when ThreadA finishes executing, the waiting thread will switch from WAITING to RUNNABLE

The third scenario is to call the LockSupport.park() method. When the thread calls this method, the state will change from the RUNNABLE state to the WAITING state. Calling LockSupport.unpark() can wake up the target thread, and the target thread state will change from the WAITING state to RUNNABLE state

4. RUNNABLE to TIMED_WAITING state

There are five scenarios for this state transition

In the first scenario, call the Thread.sleep(long millis) method with a timeout parameter

In the second scenario, the thread that acquires the synchronized implicit lock calls the Object.wait(long timeout) method with a timeout parameter

In the third scenario, call the Thread.join(long millis) method with a timeout parameter

In the fourth scenario, call the LockSupport.parkNanos(Object blocker, long deadline) method with a timeout parameter

In the fifth scenario, call the LockSupport.parkUntil(long deadline) method with a timeout parameter

The difference between TIMED_WAITING and WAITING states is that there is an additional timeout parameter

5. The difference between RUNNABLE and WAITING states

After the thread executes the run() method, it will automatically switch to the TERMINATED state. Of course, if an exception is thrown when the run() method is executed, the thread will also be terminated

Interruption strategy for threads

We may sometimes terminate the running of threads, such as some middleware, which may need to terminate some running threads during normal shutdown

Thread provides a stop() method, which will terminate the thread directly without giving any chance to breathe

But there is also a problem with such a violent termination of the thread, that is, if the thread is performing some tasks, it happens that the thread holds the ReentrantLock lock. Once we terminate the thread through violence, the thread will not automatically call the unlock() of ReentrantLock to Release the lock, then the lock will never be released, so the JDK official does not recommend using the stop method to terminate the thread, and this method is also marked as obsolete

Thread provides us with an interrupt() method, which is relatively more elegant

When this method is called, the thread will not be terminated, but a termination mark will be marked on the thread to mark it as a terminated thread. In subsequent processing, we only need to call the isInterrupted() method to determine whether the thread is terminated Thread, if it is, we will do termination processing, and we can also ignore this termination mark

In addition to calling isInterrupted() to actively detect, the other is to detect through exceptions

If we call wait(), join(), sleep() methods, an InterruptedException will be thrown. The trigger condition of this exception is: other threads call the interrupt() method of this thread

Communication between threads

Threads are not completely independent of each other, and some kind of communication mechanism is needed to achieve the purpose of mutual assistance

Object provides us with three methods: wait(), notifyAll(), and notify(). When a thread calls the wait() method, it will enter the waiting process. Calling notifyAll() and notify() will wake up the waiting thread.

The difference between notifyAll() and notify() is:

notify() wakes up a single thread that is waiting on this object’s monitor. If there are multiple threads waiting, choose one of them to wake up randomly (determined by the scheduler), and the awakened thread has the right to compete for resources fairly

notifyAll() wakes up all threads that are waiting for this object monitor, and all awakened threads compete for resources fairly

Let’s illustrate this with a code example

public class Thread01 extends Thread{<!-- -->

    private Object lock;
    
    public Thread01(Object lock){<!-- -->
         this. lock = lock;
    }

    @Override
    public void run() {<!-- -->
        synchronized(lock){<!-- -->
            try {<!-- -->
                // Call the wait method, the thread enters waiting
                lock. wait();
            } catch (InterruptedException e) {<!-- -->
                e.printStackTrace();
            }
            System.out.println(super.getName() + "was awakened...");
        }
    }
}
public static void main(String[] args) throws Exception {<!-- -->
        Object lock = new Object();
        Thread01 thread01 = new Thread01(lock);
        thread01.setName("Thread 1");
        // After starting the thread, the thread will be blocked and wait
        thread01.start();
        
        Thread. sleep(3000);
        synchronized (lock){<!-- -->
            // wake up the waiting thread
            lock. notify();
        }
}

I have explained the specific details in the comments. If you are interested, you can run this code yourself.

This communication mechanism has disadvantages, so it is rarely used. JUC provides a LockSupport class. This class provides two methods, part() and unpark(), which correspond to thread blocking and wake-up respectively. Generally, this method is used to make threads Communication

Conclusion

The code word is not easy, and I hope you can like it more, collect it and support it