Keywords:

Threads, thread pools, single threads, multithreading, benefits of thread pools, thread recycling, creation methods, core parameters, underlying mechanisms, rejection policies, parameter Settings, dynamic monitoring, thread isolation

Threads and thread pool related knowledge, is the Java knowledge, learning or interview will meet this we will from the threads and processes, parallel and concurrent, single-threaded and multithreading, etc., have been explained to the thread pool, the benefits of the thread pool, creation method, the core of the important parameters, several important methods, the underlying implementation, refused to strategy, parameter setting, dynamic adjustment, Thread isolation and so on. The main outline is as follows (this article covers only threads; thread pools are covered in the next article) :

Processes and threads

From thread to process

Talking about thread pools, we have to talk about threads first. What is a thread?

A thread (English: thread) is the smallest unit in which an operating system can schedule operations. It is contained within the process and is the actual operating unit within the process.

So the question is, what is the process?

A process is the basic unit of protection and resource allocation in an operating system.

Is it a little confusing? Is the process tangible and visible? How do you do it? If you open the Windows Task Manager or the Mac activity Monitor, you’ll see that each App you open is basically a process, but it doesn’t have to be. An App can have multiple processes.

For example, the following Typora shows two processes, each followed by a PID that is uniquely identified and assigned by the system. In addition, each process can see how many threads are executing. For example, wechat has 32 threads executing. ** An important word: ** There is at least one process after a program runs, and a process can contain multiple threads.

Why do YOU need processes?

A program is a collection of instructions, and a collection of instructions is basically a file, and the program that makes the program run, the program that executes, is the process. A program is a static description text, while a process is an execution activity of the program, which is dynamic. A process is a running program that has resources allocated by the computer.

It is impossible for a computer to have only one process, just as it is impossible for the whole country to have only one city or one department. The computer is a giant, and its operation needs to be organized, so it needs to be divided into relatively independent units according to their functions and managed separately. Each process has its own responsibilities as well as its own independent memory space. It is impossible to use it all together. If all programs share one process, it will be chaotic.

** Each process has its own memory, memory address isolation between processes, process resources such as: code snippets, data sets, heaps, etc., may also include some open files or semaphores, which are each process’s own data. ** At the same time, due to the isolation of processes, even if one process has a problem, it generally does not affect the use of other processes.

In Linux, a process has an important component called the process control block (PCB).

PCB is the unique identifier of a process. It is implemented by linked list for dynamic insertion and deletion. When a process is created, a PCB is generated, and when the process is finished, the PCB is recycled. The PCB mainly contains the following information:

  • Process status
  • Process identification information
  • The timer
  • User visible registers, control state checkboxes, stack Pointers, and so on.

How does the process switch?

Let’s start with a fact about computers: the CPU runs incredibly fast, so fast that only registers can nearly match its speed, but most of the time we need to read or write data from disk or memory, these devices are far too slow. (Default is single-core CPU unless otherwise specified)

If a program/process executes a task for a period of time and needs to write to the disk, the CPU is free, but other programs are also unavailable, and the CPU waits until the disk is written before executing again. This is more waste, CPU is not this program, other applications also want to use. When you’re not using the CPU, someone else is.

So the CPU resource needs to be scheduled. When program A doesn’t use it, it can be cut out for program B to use, but when program A cuts back, how can it be sure that it can continue to execute where it left off? The context has to be brought up.

When program A (assuming A single process) abandons the CPU, it needs to save the current context. ** What is the context? ** is the state except CPU, register or other, just like the crime scene, need to take A picture, otherwise, after the execution of other programs, how to know how to execute program A next, which step was executed before. Summary: Save the execution state of the current program.

Context switching also involves the overhead of the cache, that is, the cache will be invalidated. During context switching, the CPU will cache some data for faster execution. Once context switching is performed, the original cache will be invalidated and needs to be recached.

There are generally two kinds of scheduling (usually based on the thread dimension), and CPU time is divided into very small time slices:

  • Time – sharing: Each thread or process is used in turnCPUAverage time allocated per thread or process.
  • Preemptive scheduling: threads/processes with higher priorities preempt the next slice immediately, and if the priorities are the same, a process is randomly selected.

The time slice is super short, the CPU is super fast, and it gives us an extremely smooth feeling, as if multiple tasks are going on simultaneously

Our current operating system, or any other system, is basically preemptive scheduling. Why?

If time sharing is used, it is difficult to achieve real-time response. When the background chat program is conducting network transmission, the time slice allocated to it has not been used up, so I click the browser, there is no way to respond in real time. In addition, if the previous process dies but continues to hog the CPU, subsequent tasks will never be executed.

Due to the processing capacity of THE CPU is super fast, even a single-core CPU, running multiple programs, multiple processes, after preemptive scheduling, each program is used as if the CPU is exclusive as smooth. Processes improve CPU usage, but context switching costs processes.

What is the relationship between threads and processes?

So if we have processes, why do we need threads, multiple applications, if we have n things to do for each application, can’t we just use n processes?

Yes, but there’s no need.

Processes are generally composed of programs, data sets and process control blocks. The same application generally needs to use the same data space. If an application has many processes, even if it has the ability to share data space, the process context switch will consume a lot of resources. (Generally, an application does not have many processes. Most applications have one process and a few processes.)

The granularity of the process is relatively large, and context switch is required for each execution. If the code segments A, B and C in the same program do different things, and if they are distributed to multiple processes, the process context will be switched for each execution. This is too sad. The task of an application is a family, living in the same room (same memory space), is it necessary to treat each room as each household, go to the police station to register as a household account?

Process shortcomings:

  • Information sharing is difficult and space is independent
  • Need to switchfork()Context switching is expensive
  • You can only do one thing at a time
  • If a process is blocked and waits for data to arrive from the network, other tasks that do not depend on the data cannot do so

But some people say, well, I have a lot of things to do with one application, I can’t just have one process, and it’s going to do everything, right? Wouldn’t that clog up?

Sure, a single process can’t handle the problem, so wouldn’t it be better to divide the process into smaller ones, with many threads, and a family, where everyone has their own work to do, so that each of us is a thread and the family is a process?

A process is a time fragment that describes the scheduling of CPU time slices, while a thread is a smaller time fragment with different granularity. Threads can be called lightweight processes. In fact, threading is not an original concept, but a concept abstracted from the development of computers, which require more and more context switching for multiple tasks. Process time = time of CPU loading context +CPU execution time + time of CPU saving program context Process time = time of CPU loading context +CPU execution time + The CPU saves the program context time process time = the CPU loads the program context time +CPU execution time + the CPU saves the program context time


Thread time = C P U The time when the thread context is loaded + C P U The execution time + C P U The time to save the thread context Thread time = time when CPU loads thread context + time when CPU executes + time when CPU saves thread context

And most important, process context switching time is far higher than the cost of thread context switching time, if it is the same process of different thread preemption to CPU, switching costs will be lower, because they share the process’s address space, the communication between threads easier, by sharing process level global variables can be realized.

In addition, today’s multi-core processors, let different processes run on different cores, and the threads in the process switch on the same core to minimize (cannot be avoided) process context switching, or let different threads run on different processors, further improve efficiency.

The process and thread models are as follows:

Differences or advantages between threads and processes

  • A thread is the smallest unit of program execution, and a process is the smallest unit of resources allocated by the operating system.
  • An application may have multiple processes, and a process consists of one or more threads
  • Processes are independent from each other, and the cost of communication or communication is high. Threads in the same process share the memory of the process, and the cost of communication or collaboration is low.
  • Threads can switch context much faster than processes.

What states do threads have

A Thread can only be in one state at a given point in time. These states are virtual machine states and do not reflect any operating system Thread states. There are six or seven states in total:

  • NEW: The thread object is created, but the Start() method has not been called, and threads that have not been started are in this state.

  • There are two states, but Java threads call both ready and Running runnable

    • RunnableReady state: called after the object is createdstart()Method, the thread in that state is still in the runnable thread pool, waiting to be scheduled and fetchedCPUThe right to use the
      • Just qualified to execute, not necessarily will execute
      • start()And then in the ready state,sleep()The end orjoin()The thread enters this state when it acquires an object lock, etc.
      • CPUEnd of time slice or active callyield()Method will also enter this state
    • Running: get to theCPUThe right to use (get CPU time slices) becomes running
  • BLOCKED: A thread that blocks a lock and waits for a monitor lock, typically a method or code block modified with the Synchronize keyword

  • WAITING: state in which one thread is WAITING indefinitely for another thread to notify or interrupt.

  • TIMED_WAITING: timeout waiting, automatically wakes up after a specified time, returns, does not wait forever

  • TERMINATED: The thread is TERMINATED. If terminated call start (), will be thrown. Java lang. IllegalThreadStateException anomalies.

You can see that Thread. Java has a State enumeration class that enumerates the various states of threads (Java threads call ready and running collectively runnable) :


public enum State {
    /** * The thread state of the threads that have not been started. * /
    NEW,

    /** * Thread state of runnable thread. A runnable thread is executing in the Java virtual machine, but it may be waiting for additional resources from the operating system (such as the processor). * /
    RUNNABLE,

    /** * The thread state of the thread blocked while waiting for the monitor lock. * The blocked thread is waiting for a monitor lock to enter a synchronized block/method, or to re-enter a synchronized code block after calling the oject.wait () method */
    BLOCKED,

    /** * The thread state of the waiting thread, which is in the wait state due to calling one of the threads */
    WAITING,

    /** * The thread state of a waiting thread with a specified wait time. The thread is in a timed wait state due to calling one of the threads. * /
    TIMED_WAITING,

    /** * Terminates the thread state of a thread that has completed execution. * /
    TERMINATED;
}
Copy the code

In addition, the Thread class has several other properties associated with Thread objects:

  • Long TID: indicates the id of a thread
  • Char name[] : indicates the thread name
  • Int priority: indicates the priority of the thread
  • Boolean daemon: Indicates whether a daemon thread exists
  • Runnable target: method that the thread needs to execute

Let’s talk about some of the important ways in which threads can change their state:

  • Thread.sleep(long): After the call, the thread entersTIMED_WAITINGState, but does not release the object lock, to the time to wake up after enteringRunnableThe ready state
  • Thread.yield(): The thread calls this method to abandon the acquiredCPUTime slice, but does not release the lock resource, also becomes ready state, waiting for rescheduling, does not block, but is not guaranteed to yieldCPU“Will probably be re-elected.
  • thread.join(long): The current thread calls another threadthreadthejoin()Method, the current thread will not release the lock, will enterWAITINGorTIMED_WAITINGState: The current thread enters the ready state until the thread completes execution or the time is up.
  • object.wait(long): of the current thread calling the objectwait()Method, the current thread releases the acquired object lock and enters the wait queue,WAITINGUntil the time is up or awakened.
  • object.notify(): Wakes up the threads waiting on the object monitor, randomly picking one
  • object.notifyAll(): Wakes up all threads waiting on the object monitor

Single thread and multi-thread

Single thread, that is, there is only one thread in the execution of the task, serial execution, and multi-threading, that is, multiple threads at the same time, the so-called simultaneous, is not necessarily true at the same time, if on a single-core machine, it is false at the same time, it just looks like the same time, but actually takes turns to occupy the CPU time slice.

Each cell below is a time slice (each time slice is actually extremely short), and different threads can actually preempt different time slices to gain execution rights. Time slices are allocated in units of threads, not processes, which are containers

How do I start a thread

Java’s main() method essentially starts a single thread, but it doesn’t have a single thread in it. There are actually five threads, and the main thread is the fifth, mostly background thread:

public class Test {
    public static void main(String[] args) { System.out.println(Thread.currentThread().toString()); }}Copy the code

Execution Result:

Thread[main,5,main]
Copy the code

You can see that the above thread is the main thread, but there are four ways to create a different thread from the main thread:

  • Custom class to implementRunnableinterface
  • inheritanceThreadClass, rewriterun()methods
  • throughCallableandFutureTaskCreate a thread
  • Thread pools start directly (not per se)

Implement the Runnable interface

class MyThread implements Runnable{
    @Override
    public void run(a){
        System.out.println("Hello world"); }}public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyThread());
        thread.start();
        System.out.println("Main Thread"); }}Copy the code

Running results:

Main Thread
Hello world
Copy the code

If we look at the bottom, we can see that when we pass in the implementation object of Runnable, the implementation object of Runnable will be saved:

    public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }
Copy the code

When the start() method is called, the native start0() method is called. The native start0() method is written in C or C ++.

    public synchronized void start(a) {
        if(threadStatus ! =0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
          	// Call native methods formally
            start0();
            started = true;
        } finally {
            try {
                if(! started) { group.threadStartFailed(this); }}catch (Throwable ignore) {
            }
        }
    }
Copy the code

Start0() does call the run() method at the bottom, not directly, but by enabling another thread to do so. This is made clear in the code comments, so we won’t expand on it here. Instead, we’ll focus on the run() method. The run() method of the Runnable implementation class is called:

    @Override
    public void run(a) {
        if(target ! =null) { target.run(); }}Copy the code

Thread class inheritance

Since the Thread class itself implements the Runnable interface, we can simply inherit it:

class Thread implements Runnable {}Copy the code

Override the run() method after inheritance:

class MyThread extends Thread{
    @Override
    public void run(a){
        System.out.println("Hello world"); }}public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyThread());
        thread.start();
        System.out.println("Main Thread"); }}Copy the code

The result is the same as above. In fact, the two methods are essentially the same: one implements the Runnable interface, and the other inherits the Thread class that implements the Runnable interface. Neither method returns a value because the run() method returns void.

Callable and FutureTask create threads

To use this approach, follow these steps:

  • createCallableInterface implementation class, implementationcall()methods
  • createCallableImplements an object instance of the class withFutureTaskWrapper the implementation class instance of Callable, wrapped asFutureTaskAn instance of theFutureTaskThe instance is encapsulatedCallableThe object’sCall()Method return value
  • useFutureTaskObject as aThreadThe object’stargetCreate and start a thread,FutureTaskTo achieve theRunnableFuture.RunnableFutureinheritedRunnable
  • callFutureTaskThe object’sget()To get the return value of the end of execution of the child thread
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) throws Exception{

        Callable<String> callable = new MyCallable<String>();
        FutureTask<String> task = new FutureTask<String>(callable);

        Thread thread = newThread(task); thread.start(); System.out.println(Thread.currentThread().getName()); System.out.println(task.get()); }}class MyCallable<String> implements Callable<String> {
    @Override
    public String call(a) throws Exception {
        System.out.println(
                Thread.currentThread().getName() +
                        " Callable Thread");
        return (String) "Hello"; }}Copy the code

Execution Result:

main
Thread-0 Callable Thread
Hello
Copy the code

In fact this way is essentially a Runnable interface to realize, just did a series of packaging, but the difference is that it can realize the return value, if we are looking forward to a thing can get the result by another thread, but it may takes some time, such as asynchronous network request, actually you can consider this way.

Callable and FutureTask were added later to accommodate multiple concurrent scenarios. The differences between Callable and Runnable are as follows:

  • CallableThe way to define it iscall().RunnableThe way I defined it isrun()
  • Callablethecall()Method has a return value,Runnabletherun()Method has no return value
  • Callablethecall()Method can throw an exception,Runnabletherun()Method cannot throw an exception

The thread pool starts the thread

This is also done essentially by implementing the Runnable interface and putting it into a thread pool:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class MyThread extends Thread {
    @Override
    public void run(a) {
        System.out.println(Thread.currentThread().getName() + " : hello world"); }}public class Test {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            MyThread thread = newMyThread(); executorService.execute(thread); } executorService.shutdown(); }}Copy the code

The result of the execution is as follows: the five core threads are executing continuously, irregularly, ten times, but no ten threads are created. This depends on the design and parameters of the thread pool, which will be explained later:

pool-1-thread-5 : hello world
pool-1-thread-4 : hello world
pool-1-thread-5 : hello world
pool-1-thread-3 : hello world
pool-1-thread-2 : hello world
pool-1-thread-1 : hello world
pool-1-thread-2 : hello world
pool-1-thread-3 : hello world
pool-1-thread-5 : hello world
pool-1-thread-4 : hello world
Copy the code

In summary, starting a thread is essentially dependent on the Runnable interface, both the inheritance and implementation interface.

Multithreading may cause problems

  • Consuming resources: Context switching, or creating and destroying threads, are consuming resources.
  • Race condition: Multiple threads access or modify the same object, assuming increment operationnum++, the operation is divided into three steps: readnum.numPlus 1, write it backnumInstead of atomic operation, multiple threads running across each other will produce less than expected results.
  • Visibility of memory: Each thread has its own memory (cache). Generally, modified values are stored in the cache of its own thread. It takes a certain amount of time to refresh to the main memory, so one thread may update, but another thread still gets the old value, this is invisible.
  • Execution order unpredictable: program firststart()It is determined by the system and may cause shared variables or execution results to be distorted

Concurrency and parallelism

Concurrency is when two or more events occur at the same time interval, such as when the computer computes not only data 1 but also data 2 in the same 1s. However, the two things may not be carried out at the same time at a certain moment, it is likely to be executed before grabbing the time slice, and others will execute if they can’t. However, due to the short time slice, it seems that the execution is completed at the same time within 1s. Of course, on a single-core machine, concurrency isn’t really going on at the same time, but on a multi-core machine, concurrency might actually be going on at the same time, just maybe, and that’s also called parallelism.

Parallelism is when you have multiple instructions running on multiple processors at the same time, literally at the same time.

If it is a single-core machine, it can only be processed concurrently, not in parallel. It can only divide the CPU running time and allocate it to each thread for execution. Context switch is required when executing tasks of different threads. Multicore machines, on the other hand, can be really parallel, running on multiple cores at the same time. Parallel operations are always concurrent, but concurrent operations are not necessarily parallel.

About the author

Qin Huai, author of public number [Qin Huai Grocery store], the road of technology is not at that time, the mountain is high and the water is long, even if slow, and not stop. Personal Writing Direction: Java source code analysis, JDBC, Mybatis, Spring, Redis, distributed, sword Offer, LeetCode, etc., carefully write each article, do not like the title party, do not like the flowery, mostly write a series of articles, can not guarantee that I write are completely correct, But I guarantee that what I write is done through practice or research. We hope to correct any omissions or mistakes.

What did I write about 2020?

Open Source Programming Notes

150 page sword finger Offer PDF to receive