Many of you are probably already familiar with multitasking in operating systems: the ability to run more than one program at a time.

Multithreaded programs extend the concept of multitasking at a lower level: a program performs multiple tasks simultaneously. Usually each task is called a thread, which is short for thread control. A program that can run more than one thread at a time is called multithreaded.

What is a thread

process

A process is a program running in the system. Once the program runs, it is a process.

A process can be thought of as an instance of a program running. Processes are the entities that allocate resources to the system, and each process has its own address space.

One process cannot access another process’s variables and data. If you want one process to access another process’s resources, you need to use interprocess communication, such as pipes, files, sockets.

thread

A thread is an entity of a process. It is the basic unit of CPU scheduling and dispatch. It is a smaller unit that can run independently. A thread has virtually no system resources of its own, only a few resources that are essential to running (such as a program counter, a set of registers, and a stack), but it can share all the resources owned by a process with other threads that belong to the same process.

multithreading

Multithreading refers to the ability to run multiple threads simultaneously to perform different tasks in a single program.

The purpose of multithreaded programming is to maximize the utilization of CPU resources. When a thread does not need to occupy CPU but only deals with RESOURCES such as I/O, other threads that need to occupy CPU have other opportunities to obtain CPU resources. Basically, that’s what multithreaded programming is all about.

A program that alternates multiple lines of code at the same time creates multiple threads. The CPU takes time at random. Let’s program one thing at a time and another thing at a time.

Java has built-in support for Multithreaded Programming.

A multithreaded program consists of two or more parts that run concurrently. Each such part of the program is called a Thread. Each thread has an independent path of execution, so multithreading is a special form of multitasking.

Multitasking is supported by all modern operating systems. However, there are two distinct types of multitasking: process-based and thread-based.

1. Process-based multitasking is a more familiar form. A process, “process,” is essentially an executing program. So process-based multitasking is characterized by allowing your computer to run two or more programs at the same time.

For example, process-based multitasking allows you to run the Java compiler while editing text.

2. In a thread-based multitasking environment, threads are the smallest unit of execution. This means that a program can perform two or more tasks simultaneously.

For example, a text editor can format text at the same time it is printed.

The difference between threads and processes

The internal data and state of multiple processes are completely independent, while multi-threading shares a memory space and a group of system resources, which may affect each other.

The data of the thread itself is usually only register data, and the stack used by a program to execute, so the thread switching burden is less than the process switching burden. Multithreaded programs require less overhead than multiprocess programs.

Processes are heavyweight tasks that need to be assigned separate address Spaces, interprocess communication is expensive and limited, and interprocess transformation is costly. Threads are lightweight players, they share the same address space and the same process, communication between threads is cheap, and conversion between threads is cheap.

Implementation of threads

In Java, the run method specifies a task for a thread to complete. There are two techniques for providing a run method for a thread:

  1. Inherit the Thread class and rewrite its run method. The subclass object is then created and the start() method is called.
  2. Implement the Run method by defining a class that implements the Runnable interface. Objects of this class are passed in as arguments when Thread is created, and the start() method is called.
  3. Inherit the Thread class and rewrite its run method. The subclass object is then created and the start() method is called
  4. Implement the Run method by defining a class that implements the Runnable interface. Objects of this class are passed in as arguments when Thread is created, and the start() method is called.
  5. Implement Callable interface
  6. 4. Thread pool: provides a thread queue, which holds all threads in the waiting state. It avoids the extra overhead of creation and destruction and improves the response speed.


Both methods require executing the thread start() method to allocate the necessary system resources to the thread, and scheduling the thread to run the thread’s run () method.

The start () method is the only way to start a thread. The start() method first prepares the system resources for the thread to execute, and then calls the run () method. A thread can only be started once, and it is illegal to start again.

The run () method has thread logic in it, meaning we want the thread to do something.

I usually do this using the second method. When a thread already inherits from another class, it can only be constructed using the second method, which implements the Runnable interface.

Thread

package com.java.test;

public class ThreadTest
{
    public static void main(String[] args)
    {
        TreadTest1 thread1 = new TreadTest1();
        TreadTest2 thread2 = newTreadTest2(); thread1.start(); thread2.start(); }}class TreadTest1 extends Thread{
    @Override
    public void run(a) {
        for (int i = 0; i < 1000; ++i)
        {
            System.out.println("Test1 "+ i); }}}class TreadTest2 extends Thread{
    @Override
    public void run(a) {
        for (int i = 0; i < 1000; ++i)
        {
            System.out.println("Test2 "+ i); }}}Copy the code

When we generate Thread objects using the first method, which inherits Thread, we need to override the run() method because the run() method of the Thread class does nothing at this point.

Runnable

package com.java.test; Public class ThreadTest {public static void main(String[] args) { Threadtest1 =new Thread((new threadtest1 ())); threadtest1.start(); Thread threadtest2=new Thread((new ThreadTest2())); threadtest2.start(); } } class ThreadTest1 implements Runnable { @Override public voidrun()
    {
        for (int i = 0; i < 100; ++i)
        {
            System.out.println("Hello: " + i);
        }
    }
}

class ThreadTest2 implements Runnable
{

    @Override
    public void run()
    {
        for (int i = 0; i < 100; ++i)
        {
            System.out.println("Welcome: "+ i); }}}Copy the code

When using the second method (which implements the Runnable interface) to generate thread objects, we need to implement the Runnable interface run() method, The new Thread(new RunnableClass()) is then used to generate the Thread object (RunnableClass already implements the Runnable interface), in which case the run() method of the Thread object calls the run() method of RunnableClass

A Runnable source

package java.lang;
public
interface Runnable {
    public abstract void run();
}
Copy the code

The Runnable interface only has the run() method. The Thread class implements the Runnable interface, so it implements the run() method.

When a Thread object is generated, if no name is given, the name of the Thread object is of the form thread-number. The number is automatically incrementing and is shared by all Thread objects because it is a static member variable.

Stop the thread

Thread death cannot now be done by calling stop(), but by letting the run() method end naturally. The stop() method is unsafe and deprecated.

The recommended way to stop a thread is to set a flag variable, which in the case of the run() method is a loop, that controls whether the loop continues or breaks out; When the loop jumps out, the thread ends.

Thread shared resources

Multithreaded programming is very common in the hope that multiple threads share resources, through multiple threads at the same time to consume resources to improve efficiency, but beginners are easy to fall into a coding error.


For example (*╹▽╹*)

The following code wants to execute I — on three threads at the same time so that the value of output I is 0, but the output is 2 all three times. This is because the three threads created in the main method each hold an I variable on their own, and our goal number one should be that the three threads share an I variable.

class ThreadTest1 extends Thread {
    private int i = 3;
    @Override
    public void run() { i--; System.out.println(i); } } public class ThreadTest { public static void main(String[] strings) { Thread thread1 = new ThreadTest1(); thread1.start(); Thread thread2 = new ThreadTest1(); thread2.start(); Thread thread3 = new ThreadTest1(); thread3.start(); }} output // 2 2 2Copy the code

I should write it like this

public class ThreadTest { public static void main(String[] strings) { Thread thread1 = new ThreadTest1(); thread1.start(); thread1.start(); thread1.start(); }}Copy the code

Multiple threads execute the same code

In this case, you can use the same Runnable object (see this blog post, which is a way to create threads) to populate the same Runnable object with data that needs to be shared. Synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized

Multiple threads execute code differently

In this case, two ways of thinking can be achieved (here refer to zhang Xiaoxiang’s point of view)

One: encapsulate the shared data in another object, which is then passed to each Runnable object one by one. Each thread’s manipulation of the shared data is also assigned to that object, which makes it easy to achieve mutual exclusion and communication between operations on the modified data.

Second: the Runnable object as inner classes, in a certain class member variables in a Shared data as the external class, each thread to the operation of the Shared data method is assigned to the outside class, in order to realize to share data for various operation mutex and communications, as inner classes each Runnable object call external class of these methods.

Composition: Encapsulate shared data into another object, and each thread’s operations on the shared data are assigned to that object as a member variable or local variable in the method of the external class, and each thread’s Runnable object as a member inner class or local inner class in the external class. In general, the pieces of code that need to synchronize mutex are best placed in separate methods, which are then placed in the same class, so that synchronous mutex communication between them is easier

Rough and easy

In the program, define a static variable

The life cycle of a thread

The life cycle of a thread is the process from creation to death of a thread.



Threads can have six states

Terminated New, Runnable, Blocked, Waiting, and Timed wait are Terminated.

To determine the current state of a thread, call the getState method.

1. Create state:

When a new Thread object is created with the new operator, such as new Thread(t), the Thread has not yet started to run, the Thread is in the created state, and the program has not yet started to run the code in the Thread. A thread in the created state is just an empty thread object to which no resources are allocated.

2. Running status

Once the start() method is called, the thread is in the runnable state. A runnable thread may be running and the page may not be running, depending on how long the operating system provides the thread to run.

Once a thread starts running, it doesn’t have to stay running. In fact, the running thread is interrupted so that another thread can run. The details of thread scheduling depend on the services provided by the operating system.

Keep in mind that at any given moment, a runnable thread may be running and the page may not be running, which is why this state is placed after being runnable rather than running.

3. Cannot run

Non-runnable states include blocked or wait states where the thread is temporarily inactive, does not run any code and consumes minimal resources. Until the thread scheduler reactivates it.

When a thread attempts to acquire an internal object lock that is held by another thread, it is blocked. The thread becomes non-blocking when all other threads are free to allow the thread scheduler to allow the thread to hold it.

When a thread waits for another thread to notify the scheduler of a condition, it enters the wait state by itself. This happens when the wait and JOIN methods are called.

Several methods have a timeout parameter, for example, sleep, wait, and Join. Calling them causes the thread to enter a timed wait state. This status will remain until the timeout expires or appropriate notice is given.

More colloquial:

A running thread goes into an unrunnable state when one of the following events occurs:

The sleep() method is called;

The thread calls wait() to wait for a particular condition to be satisfied;

Thread input/output is blocked.

Return to runnable state:

The thread in the sleeping state after the specified time has passed;

If a thread is waiting for a condition, another object must notify the waiting thread of the condition change through notify() or notifyAll();

If the thread is blocked because of input/output, wait for the input/output to complete.

4. Terminated state

A thread is terminated for one of two reasons:

  • A natural death because the run method exits normally
  • The run method died because an uncaught exception terminated it


Priority of the thread

Java threads have priority Settings, and high-priority threads have a higher chance of being executed than low-priority threads.

  1. If no priority is specified for the current thread, all threads are of normal priority.
  2. The priority ranges from 1 to 10. 10 indicates the highest priority, 1 indicates the lowest priority, and 5 indicates the common priority.
  3. The thread with the highest priority is given priority at execution time. However, there is no guarantee that the thread will be in a running state at startup.
  4. A running thread may always have higher priority than a thread waiting for an opportunity to run in the thread pool.
  5. It is up to the scheduler to decide which thread is executed.
  6. T.setpriority () sets the priority of the thread.
  7. Remember that the thread priority should be set before the thread start method is called.
  8. You can use constants such as MIN_PRIORITY,MAX_PRIORITY, NORM_PRIORITY to set priorities. The priority of Java threads is an integer, The value ranges from 1 (thread.min_priority) to 10 (thread.max_priority).

    public static final int MIN_PRIORITY = 1;
    public static final int NORM_PRIORITY = 5;
    public static final int MAX_PRIORITY = 10;Copy the code

It’s not. The default priority is that of the parent thread. In the init method,

Thread parent = currentThread();  
this.priority = parent.getPriority();  Copy the code

You can change the priority through the setPriority method (final and cannot be overridden by subclasses). The priority cannot exceed the range of 1 to 10. Otherwise, IllegalArgumentException will be thrown. In addition, if the thread already belongs to a ThreadGroup, the priority of the thread cannot exceed the priority of the ThreadGroup.

What is context switching

Instead of processing one task at a time, the CPU processes tasks by assigning each thread a slice of CPU time, and then switching to the next thread when the slice runs out. The time slice is very short, usually only a few tens of milliseconds, so when the CPU is executing by constantly switching threads, we hardly feel the stalled task, which makes us feel like multiple threads are executing at the same time.

The CPU uses the time slice allocation algorithm to execute tasks in a cycle. After the current task executes a time slice, it switches to the next task. However, the state of the previous task will be saved before switching, so that the state of the task can be loaded again when switching back to the task next time. The process from saving the task to reloading is a context switch.

Such switching will affect the efficiency of multithreading.

How to reduce context switching to improve the efficiency of multithreaded programs

Thread context switching can be divided into concessional context switching and preemptive context switching.

The former means that the executing thread actively releases the Cpu, which is proportional to the severity of lock contention and can be avoided by reducing lock contention.

The latter refers to the fact that a thread is forced to abandon the CPU or is preempted by another thread with a higher priority because the allocated time slice is exhausted. Generally, the number of threads is greater than the number of available CPU cores. You can adjust the number of threads to reduce the number of threads to avoid this problem.

  1. Lockless concurrent programming
  2. Reduce the use of locks and use CAS instead of locks. In the multi-threaded competition, locking and releasing locks will lead to more context switches and scheduling delays, resulting in performance problems.
  3. Use the minimum number of threads to avoid creating unnecessary threads
  4. Coroutines are used to schedule multiple tasks in a single thread and to maintain switching between multiple tasks in a single thread

Multiple threads to use CAS updates the same variables at the same time, only one thread can update the value of the variable, and all the other thread failure, failure of threads will not be suspended, but was told that failed in this contest, and try again (as long as the CPU allocated to the thread’s failed time slice can retry unceasingly, but after a time, If it still doesn’t work, the context switch is done, so it’s just reduced.