Hello, I am Xiao Hei, a migrant worker who lives on the Internet. Some time ago, the company was interviewing for new employees. I found that many of my friends had two or three years of work experience, but they didn’t have a solid grasp of some basic Knowledge of Java, so Xiao Hei decided to share some basic knowledge of Java with everyone. We’ll start this installment with Multithreading in Java.

Okay, let’s get down to business. Let’s look at what processes and threads are.

Process vs. Thread

Process is a collection of threads in the computer operating system, which is the basic unit of system resource scheduling. A running program, such as QQ, wechat, music player, etc., contains at least one thread in a process.

Thread is the smallest unit in a computer operating system that can schedule operations. A thread is really just a single piece of code that runs sequentially. For example, the subtitle display and sound playback in our music player are two independent running threads.

Having understood the difference between a process and a thread, let’s look at the concepts of concurrency and parallelism.

Concurrency VS parallelism

When there are multiple threads in operation, if the system is only one CPU, assuming that the CPU is only one kernel, is it really impossible for more than one thread at the same time, it can only divide the CPU run time into several time periods, then time allocated to each thread execution, threads run code in a time period, other threads in a hang. This approach is called Concurrent.

When the system has more than one CPU or a CPU has multiple cores, the operation of the thread may be non-concurrent. When one CPU executes a thread, another CPU can execute another thread. The two threads do not occupy CPU resources, but can execute simultaneously. This method is called Parallel.

After reading the above paragraph, do you feel as if you understand, and as if you did not understand? What concurrent? What parallel? Mamaname? What winter plum?

Don’t worry, xiaohei first give you a popular example to explain the difference between concurrency and parallelism, and then look at the above paragraph, I believe we can understand.

You’re in the middle of a meal when the phone comes, and you don’t answer the phone until after you’ve finished your meal, which means you don’t support concurrency or parallelism;

You eat in the middle of the meal, the phone comes, you go to the phone, and then eat a meal, take a phone, eat a meal, take a phone, this shows that you support concurrency;

You eat to eat half, the phone came, your sister answered the phone, you have been eating, your sister answered the phone, this is called parallel.

To sum up, the key to concurrency is the ability to handle multiple tasks, not simultaneously;

The key to parallelism is to see if you can handle multiple tasks at the same time, which requires the presence of your sister (another CPU or kernel).

Threads in Java

In Java, a high-level computer language, there are also concepts of processes and threads.

When we start a Java program using the Main method, we start a Java process that contains at least two threads, one for garbage collection.

Threads are typically created using the Thread class in Java, so let’s see how.

How threads are created

To define a custom Thread in Java code, you inherit the Thread class, create an object of the custom class, and start it by calling the start() method of that object.

public class ThreadDemo {
    public static void main(String[] args) {
        newMyThread().start(); }}class MyThread extends Thread {
    @Override
    public void run(a) {
        System.out.println("This is my custom thread."); }}Copy the code

Or implement the java.lang.Runnable interface, pass the custom java.lang.Runnable interface object as an argument to the Thread when creating an object of the Thread class, and call the start() method to start it.

public class ThreadDemo {
    public static void main(String[] args) {
        new Thread(new MyRunnable()).s
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run(a) {
        System.out.println("This is my custom thread."); }}Copy the code

Should we subclass Thread or implement the Runnable interface during actual development? There is no definitive answer. I personally prefer to implement the Runnable interface. Instances of the Runnable interface are also managed in thread pools that we’ll learn about later. Of course, we also need to be flexible according to the actual situation.

Start and stop of threads

As you can see from the above code, a thread can be started by calling the start() method after it is created.

new MyThread().start();
Copy the code

Note that Thread overrides the run() method of its parent class. Can we call the run() method to start a Thread? The answer is no. It is important to note that a direct call to the run() method is no different from a normal method call and does not start a new thread for execution.

So how do you stop a thread? The Thread method has a stop() method.

@Deprecated // Deprecated.
public final void stop(a) {
    SecurityManager security = System.getSecurityManager();
    if(security ! =null) {
        checkAccess();
        if (this != Thread.currentThread()) {
            security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
        }
    }
    if(threadStatus ! =0) {
        resume(); 
    }
    stop0(new ThreadDeath());
}
Copy the code

However, we can see that this method is annotated with @deprecated, meaning that this method is Deprecated by the JDK. The reason for this deprecation is that using stop() forces the thread to stop, which is not safe for the program running in the thread, just like when you are taking a dump and someone is forcing you to stop. So you can’t use the stop method when you need to stop the formation.

There are two ways to properly stop a thread:

The first is to terminate a thread with a flag bit

class MyRunnable implements Runnable {
    private volatile boolean exit = false; // The volatile keyword ensures that the current thread can see the value of the main thread after it changes (visibility)
    @Override
    public void run(a) {
        while(! exit) {// Loop to determine whether to exit
            System.out.println("This is my custom thread."); }}public void setExit(boolean exit) {
        this.exit = exit; }}public class ThreadDemo {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        new Thread(runnable).start();
        runnable.setExit(true); // Change the flag bit to exit the thread}}Copy the code

Define a flag bit in the thread, decide whether to continue to execute by judging the value of the flag bit, in the main thread by modifying the value of the flag bit to stop the thread.

Second: use interrupt() to interrupt the thread

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        MyRunnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(10);
        t.interrupt(); // Attempts to interrupt the thread}}class MyRunnable implements Runnable {
    @Override
    public void run(a) {
        for (int i = 0; i < 100000; i++) {
            System.out.println("Thread executing ~"+ i); }}}Copy the code

The important thing to note here is that the interrupt() method does not stop the thread as quickly as using flag bits or stop(). If you run the code above, thread T is not interrupted. So how do I stop thread T? At this point, focus on the other two methods of the Thread class.

public static boolean interrupted(a); // Determine whether it is interrupted and clear the current interrupted state
private native boolean isInterrupted(boolean ClearInterrupted); // Determine whether the interrupted status is clear through ClearInterrupted
Copy the code

So let’s modify the above code again.

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        MyRunnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(10); t.interrupt(); }}class MyRunnable implements Runnable {
    @Override
    public void run(a) {
        for (int i = 0; i < 100000; i++) {
            //if (Thread.currentThread().isInterrupted()) {
            if (Thread.interrupted()) {
                break;
            }
            System.out.println("Thread executing ~"+ i); }}}Copy the code

At this point thread T will be interrupted.

There is no difference between this method and the above method of passing flag bits. Both methods are to judge a state and then decide whether to terminate execution. What is the difference between these two methods? There is another thing called thread state. If thread T is in sleep() or wait(), the thread cannot be interrupted immediately, but only after sleep() or wait() is awakened. In the second way, if the interrupt() method is called while the thread is asleep, an InterruptedException is thrown and the thread continues execution.

Thread state

In addition to running and stopping, there are other methods such as wait() and sleep(), which can make a thread enter a waiting or sleeping state. What are the specific states of a thread? We can actually find some answers in the code. The Thread class has an enumeration class called State, which defines the six states of a Thread.

public enum State {
    /** * The thread state of the thread that has not been started */
    NEW,
    /**
     * 可运行状态
     */
    RUNNABLE,
    /** * Blocked */
    BLOCKED,
    /** * Wait status */
    WAITING,
    /** * Timeout wait status */
    TIMED_WAITING,
    /** * Terminates status */
    TERMINATED;
}
Copy the code

So how do these six states change in the thread? When a thread is RUNNABLE and when it is BLOCKED, we use the following diagram to show how its state changes.

Thread status details

Initialization State (NEW)

When a Thread instance is new, the state of the Thread object is the initialization state.

RUNNABLE state

  1. After the call to start () method, the runnable threads will arrive, note that the runnable doesn’t mean they must be on the run, because the operating system’s CPU resources to rotate execution (i.e., the concurrent) began to say, to wait for the operating system scheduling, only will be scheduled to start, so here just reached the READY state (READY), Note The system is eligible for scheduling.
  2. When the system schedules a thread, the thread reaches the RUNNING state. In this state, if the thread uses up the CPU time slice or calls yield(), it will return to the ready state and wait for the next time to be scheduled.
  3. When a dormant thread is notified (), it enters the ready state.
  4. The Thread that is park(Thread) is unpark(Thread), and enters the ready state.
  5. The thread will enter the ready state when the timeout waiting time expires.
  6. When a synchronized code block or method acquires a lock resource, it enters the ready state.

Timeout waiting (TIMED_WAITING)

When a thread calls methods like sleep(long), join(long), or lock objects in synchronous code call wait(long), and locksupport.arknanos (long), Locksupport.parkuntil (long) Either of these methods puts the thread into a timeout wait state.

WAITING.

The main difference between the wait state and timeout wait state is that there is no specified amount of time to wait. Methods such as thread.join (), lock object call wait(), locksupport.park (), etc., put threads into the wait state.

They are BLOCKED.

The blocked state mainly occurs when some resources are obtained. The blocked state will be entered before the resource is successfully obtained, and the ready state will be entered after the resource is successfully obtained.

TERMINATED (TERMINATED)

The termination state is understood as the termination state when the current thread finishes executing. The thread object may be alive at this point, but there is no way to make it execute again. The so-called “thread” can’t come back from death.

Thread important method

From the previous section, we saw that there are many methods called to change thread states, such as Join(), yield(), wait(), notify(), and notifyAll().

Start (), run(), interrupt(), isInterrupted(), interrupted(), and interrupted() have all been mentioned above, so we won’t go into too much detail here.

The /** * sleep() method lets the current thread sleep for a certain amount of time and throws an InterruptedException. * This exception is not a runtime exception and must be caught and handled. It is raised if the thread is interrupted while sleeping (). * Once an exception is thrown, the flag bit will be cleared. If the exception is not handled, the next loop will not catch the interrupt, so the flag bit is usually set during exception handling. The sleep() method does not release lock resources for any object. * /
public static native void sleep(long millis) throws InterruptedException;

/** * yield() is a static method that, once executed, causes the current thread to yield CPU. Relinquishing CPU does not mean that the current thread will not execute, but will also compete for CPU resources. * If a thread is not important or has a low priority, you can use this method to give resources to the important thread. * /
public static native void yield(a);
The /** * join() method represents an infinite wait, which blocks the current thread until the target thread finishes executing. * /
public final void join(a) throws InterruptedException ;
/** * join(long millis) gives a maximum wait time, if the target thread is still executing after the given time, the current thread will not wait and continue to execute. * /
public final synchronized void join(long millis) throws InterruptedException ;

Copy the code

The above methods are methods in the Thread class, and as you can see from the method signature, the sleep() and yield() methods are static, while the Join () methods are member methods.

Wait (),notify(), and notifyAll() are methods of the Object class. These three methods are mainly used for communication between threads competing for shared resources in synchronized methods or synchronized code blocks.

/** * causes the current thread to wait until another thread calls notify() or notifyAll() of the object. * /
public final void wait(a) throws InterruptedException
/** * Wakes up a single thread that is waiting for the object monitor. * /
public final native void notify(a);
/** * Wakes up all threads that are waiting on the object monitor. * /
public final native void notifyAll(a);
Copy the code

A typical example of notify/notifyAll() for wait() is producer-consumer.

The scenario is as follows:

Suppose there is a KFC and there are hamburgers on sale. In order to keep the hamburgers fresh, the clerk will make no more than 10 hamburgers. Then customers will come to buy the hamburgers. When the number of burgers reaches 10, the employee stops making them, and when the number reaches zero, the customer has to wait for new burgers to be made.

We now simulate this scenario with two threads, one for making and one for buying. The code is as follows:

class KFC {
    // The number of burgers
    int hamburgerNum = 0; 
    
    public void product(a) {
        synchronized (this) {
            while (hamburgerNum == 10) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Produce a burger." + (++hamburgerNum));
            this.notifyAll(); }}public void consumer(a) {
        synchronized (this) {
            while (hamburgerNum == 0) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Sell a burger." + (hamburgerNum--));
            this.notifyAll(); }}}public class ProdConsDemo {
    public static void main(String[] args) {
        KFC kfc = new KFC();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) { kfc.product(); }},"C").start();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) { kfc.consumer(); }},"Customer").start(); }}Copy the code

As you can see from the code above, these three methods are used together.

Wait () and notify/notifyAll() are local final methods of Object and cannot be overridden.

Wait () blocks the current thread if the lock has been acquired, and is used with the synchronized keyword.

When a thread executes wait(), it releases the current lock, then releases the CPU, and enters the wait state.

Since wait() and notify/notifyAll() execute in a synchronized block, the current thread must have acquired the lock. Only when notify/notifyAll() is executed does one or more of the waiting threads wake up and continue until a synchronized block is executed or a wait() is encountered and the lock is released again.

Note that after notify/notifyAll() wakes up a sleeping thread, the thread continues with the last execution. Therefore, if should not be used in conditional judgment. If there are multiple customers to buy, if they go directly to buy without making judgment after being woken up, they may have been finished by another customer. Therefore, while judgment must be used to judge again after being woken up.

Last but not least, the difference between wait() and sleep() is that sleep() can be executed anywhere and anytime, not necessarily in a synchronized block, so a call in a synchronized block does not release the lock, whereas wait() must be called in synchronized code and releases the lock.


Well, that’s all for today. I’m Hei, and I’ll see you next time.

If you like xiaohei, you can also pay attention to my wechat public number [Xiaohei said Java], dry goods.