Java Thread Foundation, Start with this

Keywords: Programming Java Dubbo network JDK

Threads, as the smallest dispatching unit in the operating system, generally have multicore processors in the current operating environment. In order to make better use of CPU, master its correct use, and make the program run more efficiently.It is also an extremely important module in Java interviews.

Introduction to Threads

A program that runs independently is a process that can contain one or more threads, each with its own properties, such as stacks, counters, and so on.At the same time, a thread can only run on one CPU processor core at a point in time, and shared variables can also be accessed between different threads.When a thread is running, the system assigns a number of CPU time slices to each thread, and the CPU runs a thread during that time slice. When the time slice runs and jumps to the next time slice, the CPU switches among these threads at a high speed, making the program appear to be multi-threaded at the same time.

Thread implementation

There are two common ways to implement threads: inherit the java.lang.Thread class and implement the java.lang.Runnable interface.

Inherit Thread Class Method

Get threads by instantiating the java.lang.Thread class.Create a Thread object, typically by inheriting the Thread class, and then overriding some methods of Thread by method overrides.

First, create a subclass that inherits Thread.

public class DemoThread extends Thread{

    // Override run method in Thread class
    @Override
    public void run() {
        // currentThread().getName() Gets the current thread name
        System.out.println("java.lang.Thread Created"+ currentThread().getName() +"thread");
    }
}

The object instantiated by DemoThread in the code above represents a thread, which is implemented logically in the run method by overriding the run method.

public class Main {

    public static void main(String[] args) {
        // Instantiate DemoThread to get a newly created thread instance
        DemoThread thread = new DemoThread();
        // Name the created child thread
        thread.setName("DemoThread Child Thread");
        // Start Thread
        thread.start();
        
        // Print information through the main thread
        System.out.println("main thread");
    }

}

Creates a sub-thread in the main thread of program execution, named DemoThread, and prints the information printed by the main thread at the end of the program.The calling thread must call the start() method. Before calling this method, the child thread does not exist. Only after the start() method is called, the thread is actually created.

Execution results:

As you can see from the results, since a sub-thread is created in the main thread, the sub-thread is equivalent to an asynchronous operation relative to the main thread, so it is possible that the main thread performs the print operation before the sub-thread.

Implement Runnable Interface

Since Java is a single inheritance feature, inheritance cannot be achieved when the subclass that created the thread inherits other classes.This allows you to implement the logic for thread creation by implementing the Runnable interface.

First, create a class that implements Runnable.

public class DemoRunnable implements Runnable {
    
    // Implementation of run Method in Runnable
    @Override
    public void run() {
        System.out.println("java.lang.Runnable Created "+ Thread.currentThread().getName() +"thread");
    }
}

There is a run method defined in the Runnable interface, so to implement the Runnable interface, you must implement the run method.In fact, the java.lang.Thread class also implements the Runnable interface.

Create Threads:

public class Main {

    public static void main(String[] args) {
        // Create an instance of Thread and name the thread that will be created
        Thread thread = new Thread(new DemoRunnable(), "DemoRunnable Child Thread");
        // Create a thread
        thread.start();

        System.out.println("main thread");
    }

}

results of enforcement

The same result is achieved as inheriting Thread.

When a Thread instance is created, a parameter to an object that implements the Runnable interface is passed into the newly created Thread instance.

Initialize the specific implementation of Thread#init in Thread:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        // Assign a thread name to the currently created thread instance
        this.name = name;
        // The parent thread of the thread to be created is the current thread
        Thread parent = currentThread();
        // Add to Thread Group Operation
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }

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

        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        // Number of threads added as startup in thread group
        g.addUnstarted();
        this.group = g;
        // Set some properties of the parent thread to the thread currently being created
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        
        // Assign the current parameter passed into the target to the current Thread object to hold an instance of the implemented Runnable interface
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        
        // Set stack size for threads
        this.stackSize = stackSize;

        // Give the thread created an id
        tid = nextThreadID();
    }

The init method used by the code above to create a threaded object that is held in the threaded object by passing in an instance object of Runnable.

Once the threadobject is created, the start() method is called, and the thread runs the run() method that holds the Runnable implementation class object.

For example, the case in this article executes the logic of the DemoRunnable#run method.

These two methods create threads, which one to use, depending on your needs.If you need to inherit other non-Thread classes, you need to use the Runnable interface.

Thread state

Java threads exist in one of six states at each point in time.

state describe
NEW Initial state, before the thread object calls the start() method
RUNNABLE Running state, ready or running after thread start()
BLOCKED Blocking state, lock state after thread acquires lock
WAITING Wait state, threads enter wait state, no time slices are allocated, waiting for other threads to wake up
TIME_WAITING Timeout wait state, also no time slice allocated, automatically wakes up when the time has reached the set wait time
TERMINATED Termination state, indicating that the current thread execution is complete

Among them, NEW, RUNNABLE, TERMINATED are well understood, and now mainly for BLOCKED, WAITING and TIME_WAITING case explanation.

BLOCKED

A blocking state is a competitive relationship between two threads that locks when run is called.

The first step is to use the above Runnable implementation.

public class DemoRunnable implements Runnable {

    @Override
    public void run() {
        // Do an infinite loop without exiting by adding a synchronous lock to DemoRunnable
        synchronized (DemoRunnable.class){
            while (true){
                System.out.println("java.lang.Runnable Created "+ Thread.currentThread().getName() +"thread");
            }
        }
    }
}

Threads that compete until the DemoRunnable class enters the run will continue to execute, and threads that do not compete will remain blocked.

Create two threads

public class Main {
    
    public static void main(String[] args) {
        // Create two-threaded tests
        new Thread(new DemoRunnable(), "test-blocked-1")
                .start();
        new Thread(new DemoRunnable(), "test-blocked-2")
                .start();
    }

}

By analyzing the executed thread, see the following figure:

You know that the thread test-blocked-1 is competing against the DemoRunnable class and has been running a while loop, so the state is RUNNABLE.test-blocked-2 has been blocked by BLOCKED because the DemoRunnable#run class is locked synchronously.

WAITING

Wait state threads are not allocated CPU time slices. If a thread is to be waked up again, it must be shown to be waked up by other threads or it will wait forever.

An example of implementing a wait state

public class DemoRunnable implements Runnable {

    @Override
    public void run() {
        while (true){
            // Call the wait method so that the thread is waiting on the current instance
            synchronized (this){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("java.lang.Runnable Created "+ Thread.currentThread().getName() +"thread");
            }
        }
    }
}

// Create Threads
public class Main {
    
    public static void main(String[] args) {
        new Thread(new DemoRunnable(), "test-waiting")
                .start();
    }

}

After creating the instance thread, analyze the test-waiting thread, which is in the WAITING state.

TIME_WAITING

A timeout wait state thread is also not allocated a CPU time slice, but it automatically wakes up the current thread after a set interval.That is, adding a time limit to the thread in the wait state is the timeout wait state.

Simply increase the wait time limit for the above WAITING status cases.

public class DemoRunnable implements Runnable {

    @Override
    public void run() {
        while (true){
            synchronized (this){
                try {
                    // Increase wait time
                    this.wait(1000000, 999999);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("java.lang.Runnable Created "+ Thread.currentThread().getName() +"thread");
            }
        }
    }
}

Analyzing the results of the thread, you can see that the test-time_waiting thread is in a timeout wait state, which is also a timeout wait state when sleep ing.

Transition between thread states, as shown in Figure (Source Network):

Common Thread Methods

currentThread()

CurrtThread is to get the current thread instance and return the Thread object, which is a static method that uses the following

Thread.currentThread();

start()

The start method is the entry method to start a thread, which is the start method in the above implementation's Create Thread example.

run()

The run method is when a thread is created, and the thread actively calls the run method to execute the logic inside.

join()

The join method is thread synchronization, for example, in the example where the Thread method is inherited to create a thread, if the thread.join() method is called after thread.start(), the information printed by the main thread must follow the information printed by the child thread.Here the main thread waits for the child thread to finish executing before continuing.

getName()

getName returns the thread name.

getId()

Gets the thread Id, which is an Id value that returns a long type.

setDaemon()

The setDaemon(boolean on) method sets the thread type, and setDaemon accepts a boolean type parameter.When set to true, the thread type is a daemon thread, and when set to false, the thread type is a user thread.

yield()

The yield method is a thread concession that puts the current thread in a ready state to execute other threads of the same priority, but does not necessarily execute other threads. It is possible that the thread after the concession will be executed again.

setPriority()

setPriority(int newPriority) sets the priority of thread execution, with a value of 1-10 and a default value of 5. Threads with larger values execute first.

interrupt()

The interrupt method interrupts the thread, but it continues to run.It simply means that another thread has given it a break flag.

interrupted()

The interrupted method checks to see if the current thread is interrupted.The thread's interrupt flag is cleared when this method is called.

isInterrupted()

The isInterrupted method detects if the current thread is interrupted and, if interrupted, does not clear the interrupt flag.

summary

In this paper, the common functions and concepts of threads are analyzed, mainly explaining some operations of single threads. Thread operation is very easy to cause problems in production, so after mastering the concepts and uses, it is necessary to study more and think more about the design and implementation of applications.These basic uses and concepts must be mastered when multithreaded operations are mastered. Future articles on multithreaded analysis will be published.

Recommended reading

JDK Dynamic Proxy and CGLIB Dynamic Proxy that You Must Meet

Dubbo Extension Point Loading Mechanism: From Java SPI to Dubbo SPI

volatile Hands for Your Resolution

Focus on the Public Number (ytao), more original articles

Posted by bradymills on Mon, 20 Apr 2020 18:52:20 -0700