JUC Concurrent Programming: Concurrency Basics and Java Multithreading

0. Introduction & Glossary

       The purpose of concurrent programming is to make the program run faster, but starting more threads does not mean that the program can be executed concurrently to the maximum extent. When performing concurrent programming, if you want to make the program run faster through multi-threaded execution of tasks, you will face many challenges, such as context switching problems , deadlock problems , and resource limitations limited by hardware and software . - From "The Art of Concurrent Programming in Java"

0.1 Context switch

       Even single-core processors support multi-threaded execution of code. The CPU implements this mechanism by allocating CPU time slices to each thread. The time slice is the time allocated by the CPU to each thread. Because the time slice is very short, the CPU keeps switching threads for execution, making us feel that multiple threads are executing at the same time. The time slice is generally tens of milliseconds (ms) . .

       The CPU executes tasks cyclically through a time slice allocation algorithm . After the current task executes a time slice, it will switch to the next task. However, the status of the previous task will be saved before switching, so that the status of this task can be loaded again the next time you switch back to this task. So the process of a task from saving to reloading is a context switch.

       Executing multiple threads concurrently is not faster than serial execution because of the overhead of context switching.

0.1.1 How to reduce context switching

       Methods to reduce context switching includeLock-free concurrent programming, CAS algorithm, using minimal threads and using coroutines.

  1. Lock-free concurrent programming: When multiple threads compete for locks, context switching will occur. Therefore, when multiple threads process data, you can use some methods to avoid using locks. For example, the data ID is segmented and modulated according to the Hash algorithm, and different threads process different segments of data.
  2. CAS algorithm: Java's Atomic package uses the CAS algorithm to update data without locking.
  3. Use the fewest threads: Avoid creating unnecessary threads. For example, if there are few tasks but many threads are created to process them, this will cause a large number of threads to be waiting.
  4. Coroutines: Implement multi-task scheduling in a single thread, and maintain switching between multiple tasks in a single thread.

0.2 What is concurrency? What is parallelism?

       The most recognized definition of concurrency is that it uses a single core to perform multiple tasks on a single processor.In this case, the operating system's task scheduler quickly switches from one task to another, so it appears that all tasks are running at the same time.

       The same definition applies to parallelism:Running multiple tasks simultaneously on different computers, processors, or processor cores at the same time is called "parallelism."

       Another definition of concurrency is,Running multiple tasks (different tasks) on the system at the same time is concurrency.Another definition of parallelism is: running different instances of the same task on different parts of a data set at the same time is parallelism.

The above is from "Mastering Concurrent Programming in Java"

Insert image description here
The above comes from "Coding to Efficiency: Java Development Manual"

0.3 What is a process? What thread is it?

  1. Threads are the basic unit of processor task scheduling and execution.
  2. Process is the basic unit of operating system resource allocation.
  3. A process is an execution process of a program and is the basic unit of system operation.
  4. A thread is a smaller execution unit than a process, and a process can contain multiple threads.

0.4 The relationship between processes and threads

        When a modern operating system runs a program, it creates a process for it. For example, when you start a Java program, the operating system creates a Java process. The smallest unit of modern operating system scheduling is a thread, also called a lightweight process (LightWeightProcess). Multiple threads can be created in a process. These threads have their own attributes such as counters, stacks, and local variables, and can access shared memory variables. The processor switches between these threads at high speed, allowing users to feel that these threads are executing at the same time. -- From "The Art of Concurrent Programming in Java"

        Threads are the basic unit of processor task scheduling and execution, and processes are the basic unit of operating system resource allocation. A process can contain multiple threads.

        From the perspective of the Java virtual machine: the runtime data area of ​​the Java virtual machine includes the heap, method area, virtual machine stack, local method stack, and program counter. Each process is independent of each other. Each process will contain multiple threads. The multiple threads contained in each process are not independent of each other. This thread will share the heap and method area of ​​the process, but these threads will not share it. Virtual machine stack, local method stack, program counter. That is, multiple threads contained in each process share the heap and method area of ​​the process, and have private virtual machine stacks, local method stacks, and program counters. As shown in the figure, assume that a process contains three threads.
Insert image description hereFrom the above, we can know the differences between the following processes and threads in the following aspects:

        Memory allocation: The address spaces and resources between processes are independent of each other. Threads in the same process will share the thread's address space and resources (heap and method area).

        Resource overhead: Each process has its own data space, and switching between processes will have a large overhead. Threads belonging to the same process will share the heap and method area, and also have private virtual machine stacks, local method stacks, and program counters. The resource overhead of switching between threads is small.

0.5 Advantages and Disadvantages of Multithreading

Advantages: When a thread enters the waiting state or is blocked, the CPU can first execute other threads to improve CPU utilization.

shortcoming:

  1. Context switching: Frequent context switching will affect the execution speed of multi-threading.
  2. deadlock
  3. Resource limitations: When performing concurrent programming, the execution speed of the program is limited by the computer's hardware or software resources. In concurrent programming, the reason why program execution becomes faster is to turn the serially executed part of the program into concurrent execution. If the concurrently executed part is still executed serially due to resource constraints, program execution will become slower because Program concurrency requires context switching and resource scheduling.

1. Java threads

1.1 Create and run threads

1.1.1 Method 1. Use Thread directly

// 匿名内部类写法
public class MyThread {
    
    
    public static void main(String[] args) {
    
    
        new Thread() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("Hello World");
            }
        }.start();
    }
}
public class MyThread extends Thread {
    
    
	@Override
	public void run() {
    
    
		super.run();
		System.out.println("你好");
	}
}
------------------------------------------------
public class ThreadTest {
    
    
	public static void main(String[] args) {
    
    
		MyThread myThread = new MyThread();
		myThread.start();
	}
}

1.1.2 Method 2 uses Runnable with Thread

Separate [thread] and [task] (code to be executed)

  • Thread represents thread
  • Runnable Runnable task (code to be executed by the thread)
public class MyThread1 implements Runnable {
    
    
	@Override
	public void run() {
    
    
		System.out.println("你好");
	}
}

--------------------------------------------------
public class MyThread1Test {
    
    
	public static void main(String[] args) {
    
    
		MyThread1 myThread1 = new MyThread1();
		Thread thread = new Thread(myThread1);
		thread.start();
	}
}
public class MyThread {
    
    
    public static void main(String[] args) {
    
    
        Runnable runnable = new Runnable() {
    
    
            @Override
            public void run() {
    
    
            	// 要执行的任务
                System.out.println("Hello World");
            }
        };

        Thread t = new Thread(runnable,"线程名");
        t.start();
    }
}

-----------------------------------------------
// lambda表达式的写法
public class MyThread {
    
    
    public static void main(String[] args) {
    
    
        Runnable runnable = () -> System.out.println("Hello World");

        Thread t = new Thread(runnable, "线程名");
        t.start();
    }
}

Sections:
1. Use the Runnable interface to separate threads and tasks.
2. Using Runnable makes it easier to cooperate with advanced APIs such as thread pools.
3. Use Runnable to separate the task class from the Thread inheritance system and make it more flexible.

1.1.3 Method 3 FutureTask cooperates with Thread

FutureTask can receive parameters of Callable type to handle situations where results are returned.

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyThread {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 创建任务对象
        FutureTask<Integer> task3 = new FutureTask<>(() -> {
    
    
            System.out.println("Hello World");
            return 100;
        });

        // 参数1 是任务对象;参数2 是线程名字
        new Thread(task3, "t3").start();

        Integer result = task3.get();
    }
}
import java.util.concurrent.Callable;

public class MyThread3 implements Callable<String> {
    
    

	@Override
	public String call() throws Exception {
    
    
		return "hello";
	}
}
--------------------------------------------------
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Thread3Test {
    
    
	public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
		MyThread3 myThread3 = new MyThread3();
		FutureTask futureTask = new FutureTask(myThread3);
		new Thread(futureTask).start();
		System.out.println(futureTask.get());
	}
}

1.1.3 Method 4 uses thread pool

public class TestCallable implements Callable<String> {
    
    
	@Override
	public String call() throws Exception {
    
    
		return "通过线程池实现多线程";
	}
}
--------------------------------------------------
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class NewTest {
    
    
	public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
		TestCallable testCallable = new TestCallable();
		ExecutorService executorService = Executors.newFixedThreadPool(3);
		// 提交任务
		Future<String> future = executorService.submit(testCallable);
		String result = future.get();
		System.out.println(result);
		
		// 也可通过下面的lambda写法
		ExecutorService executorService = Executors.newFixedThreadPool(3);
        // 提交任务
        Future<String> future = executorService.submit(()-> "Hello");
        String result = future.get();
        System.out.println(result);
	}
}

However, Alibaba regulations do not allow the use Executorsof classes to create thread pools.
Insert image description hereInteger.MAX_VALUEThe value size is 2 31 -1 which is more than 2.1 billion
- the above comes from Alibaba's "Java Development Manual"

1.2 Thread life cycle (status)

1.2.1 Five states

This statement of five states is described from the operating system level.
Insert image description here

  1. NEWThe new state is a state in which the thread is created and has not been started.

  2. RUNNABLE, that is, the ready state, which is start()the state after calling and before running. The thread's start() method cannot be called multiple times, otherwise IllegalStateExceptionan exception will be thrown.

  3. RUNNING, that is, the running state, is the thread state when run() is being executed. Threads may exit RUNNING due to certain factors, such as time, exceptions, locks, scheduling, etc.

  4. BLOCKE, that is, the blocking state. Entering this state has the following situations:

    • Synchronous blocking: the lock is occupied by other threads.
    • Active blocking: Call a method of Thread to actively give up CPU execution rights, such as sleep(), join(), etc.
    • Waiting for blocking: wait() is executed
  5. DEAD, that is, the termination state, which is the state after the execution of run() ends or exits due to an exception. This state is irreversible.

1.2.2 Six states

This is described from the Java API level

Insert image description here

Insert image description here

Insert image description here
- Table/Figure 4-1 from "The Art of Java Concurrent Programming"

        As can be seen in Figure 4-1, after the thread is created, the start() method is called to start running. When the thread executes the wait() method, the thread enters the waiting state. Threads that enter the waiting state need to rely on notifications from other threads to return to the running state. The timeout waiting state is equivalent to adding a timeout limit on the basis of the waiting state, that is, it will return to the running state when the timeout period is reached. When a thread calls a synchronization method, the thread will enter a blocking state without acquiring the lock. The thread will enter the terminated state after executing the Runnable's run() method.

        Note: Java combines the two states of running and ready in the operating system as the running state. The blocking state is the state when the thread is blocked when entering the method or code block modified by the synchronized keyword (obtaining the lock), but the java.concurrentthread state blocked in the Lock interface in the package is in the waiting state, because the Lock interface in the java.concurrent package is not suitable for blocking. The implementation uses the relevant methods in the LockSupport class.

2. Java thread pool

        The thread pool in Java is the concurrency framework with the most application scenarios. Almost all programs that need to execute tasks asynchronously or concurrently can use the thread pool. During the development process,Reasonable use of thread pools can bring three benefits.

  1. Reduce resource consumption.Reduce the cost of thread creation and destruction by reusing created threads.
  2. Improve response speed.When a task arrives, the task can be executed immediately without waiting for the thread to be created.
  3. Improve thread manageability.Threads are scarce resources. If they are created without limit, they will not only consume system resources, but also reduce the stability of the system. The thread pool can be used for unified allocation, tuning and monitoring.

2.1 Implementation principle of thread pool

        After submitting a task to the thread pool, the thread pool processing flow is shown in Figure 9-1. As can be seen from the figure, when a new task is submitted to the thread pool, the processing flow of the thread pool is as follows.

  1. The thread pool determines whether all threads in the core thread pool are executing tasks.If not, a new worker thread is created to perform the task. If all threads in the core thread pool are executing tasks, enter the next process.
  2. The thread pool determines whether the work queue is full.If the work queue is not full, newly submitted tasks are stored in this work queue. If the work queue is full, enter the next process.
  3. The thread pool determines whether all threads in the thread pool are in working state.If not, a new worker thread is created to perform the task. If it is full, it is handed over to the saturation strategy to handle this task.

Insert image description here
– The above is from "The Art of Concurrent Programming in Java"

2.2 Executor framework

       In Java, threads are used to perform tasks asynchronously. The creation and destruction of Java threads requires a certain amount of overhead. If we create a new thread for each task to execute, the creation and destruction of these threads will consume a lot of computing resources. At the same time, a new thread is created for each task to execute. This strategy may eventually cause the application under high load to crash.
       Java's thread is both a unit of work and an execution mechanism. Starting from JDK5, the unit of work is separated from the execution mechanism. The unit of work includes Runnable and Callable, and the execution mechanism is provided by Executorthe framework ( import java.util.concurrent.Executor).

2.2.1 Introduction to the Executor Framework: Two-level Scheduling Model (TODO) of the Executor Framework

// ALL

2.2.1 Executor framework structure (mainly composed of three parts)

2.1 Three major methods

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

public class Thread3Test {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 单个线程
        ExecutorService threadPool1 = Executors.newSingleThreadExecutor();

        // 创建一个固定大小的线程池
        ExecutorService threadPool2 = Executors.newFixedThreadPool(5);

        // 可自动适应大小的线程池
        ExecutorService threadPool3 = Executors.newCachedThreadPool();
    }
}

3. How to view processes and threads

Study course address

3.1 Windows

  • Task Manager can view the number of processes and threads, and can also be used to kill processes
  • tasklist View progress
  • taskkill kill process

3.2 Linux

  • ps -fe View all processes

  • ps -fe|grep java View all Java processes

  • ps -fT -p <PID> View all threads of a process (PID)

  • kill kill process
    Insert image description here

  • top -H -p <PID>Dynamically view all threads of a process (PID)
    Insert image description here
    Insert image description here
    Insert image description here
    Insert image description here

3.3 Java

  • jps Command can view all Java processes
  • jstack <PID> View all thread status of a Java process (PID)

4. Principles and common methods of thread operation

4.1 Stack and stack frame

Java Virtual Machine Stacks

We all know that the JVM is composed of heap, stack, and method area . Who is the stack memory used for? In fact, it is a thread. After each thread is started, the virtual
machine will allocate a piece of stack memory for it.

  • Each stack is composed of multiple stack frames (Frame), corresponding to the memory occupied by each method call; each thread calling a method will generate a stack frame memory
  • Each thread can only have one active stack frame, corresponding to the method currently being executed.

4.2 Thread Context Switch

Because of the following reasons, the CPU no longer executes the current thread and instead executes the code of another thread.

  • The thread's cpu time slice has run out
  • Garbage collection
  • There is a higher priority thread that needs to run
  • The thread itself calls sleep, yield, wait, join, park, synchronized, lock and other methods

When a Context Switch occurs, the operating system needs to save the state of the current thread and restore the state of the other thread. The corresponding concept in Java
is the program counter (Program Counter Register). Its function is to remember the execution of the next jvm instruction. The address is private to the thread.

  • The status includes the program counter and information about each stack frame in the virtual machine stack, such as local variables, operand stack, return address, etc.
  • Frequent occurrence of Context Switch will affect performance

4.3 Common methods

method name Function Description Notice
start() runStart a new thread and run the code in the method in the new thread startThe method only makes the thread ready, and the code inside does not necessarily run immediately (the CPU time slice has not yet been allocated to it). The start method of each thread object can only be called once. If it is called multiple times, it will appear.IllegalThreadStateException
run() Method that will be called after the new thread is started If the Runnable parameter is passed when constructing the Thread object, the run method in Runnable will be called after the thread is started, otherwise no operation will be performed by default. But you can create a subclass object of Thread to override the default behavior
join() Wait for the thread to finish running
join(long n) Wait for the thread to finish running, up to n milliseconds
getId() Get the long integer id of the thread id unique
getName() Get thread name
setName(String) Modify thread name
getPriority() Get thread priority
setPriority(int) Modify thread priority Java stipulates that the thread priority is an integer from 1 to 10. A larger priority can increase the probability of the thread being scheduled by the CPU.
getState() Get thread status Thread status in Java is represented by 6 enums, namely: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
isInterrupt() interrupt thread If the interrupted thread is sleeping, waiting, or joining, it will cause the interrupted thread to throw InterruptedException and clear the interruption mark; if the interrupted thread is running, the interruption mark will be set; the park thread is interrupted , the break mark will also be setpublic void interrupt()
interrupted() Determine whether the current thread is interrupted Static will be cleared打断标记
isInterrupted() Determine whether it was interrupted staticpublic static boolean interrupted()
isAlive() Whether the thread is alive (it has not finished running yet)
currentThread() Get the currently executing thread static
sleep(long n) Let the currently executing thread sleep for n milliseconds, and give up the CPU time slice to other threads while sleeping. static
yield() Prompts the thread scheduler to give up the current thread's use of the CPU Static is mainly for testing and debugging

4.3.1 start() and run()

import ch.qos.logback.core.util.FileUtil;
import cn.itcast.Constants;
import cn.itcast.n2.util.FileReader;
import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.TestStart")
public class TestStart {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread("t1") {
    
    
            @Override
            public void run() {
    
    
                log.debug(Thread.currentThread().getName());
                FileReader.read(Constants.MP4_FULL_PATH);
            }
        };

        t1.start();
        log.debug("do other things ...");
    }
}
19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...

t1.run()Change the above code tot1.start()

19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms

The program runs on the t1 thread, and the FileReader.read() method call is asynchronous

summary:

  • Calling run directly means that run is executed in the main thread and no new thread is started.
  • Using start is to start a new thread and indirectly execute the code in run through the new thread.

4.3.2 sleep and yield

sleep

  1. Calling sleep will cause the current thread to enter the Timed Waiting state (blocked) from Running
  2. Other threads can use the interrupt method to interrupt the sleeping thread, and the sleep method will throw InterruptedException.
  3. The thread after sleeping may not be executed immediately
  4. It is recommended to use TimeUnit's sleep instead of Thread's sleep to obtain better readability.
package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test7")
public class Test7 {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t1 = new Thread("t1") {
    
    
            @Override
            public void run() {
    
    
                log.debug("enter sleep...");
                try {
    
    
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
    
    
                    log.debug("wake up...");
                    e.printStackTrace();
                }
            }
        };
        t1.start();

        Thread.sleep(1000);
        log.debug("interrupt...");
        t1.interrupt();
    }
}

Insert image description here


yield

  1. Calling yield will cause the current thread to enter the Runnable ready state from Running, and then schedule other threads to execute.
  2. The specific implementation depends on the task scheduler of the operating system.
  3. CPU resources will be given up and lock resources will not be released.

Thread priority

  • The thread priority will hint to the scheduler that the thread should be scheduled first, but it is only a hint and the scheduler can ignore it.
  • If the CPU is busy, threads with higher priority will get more time slices, but when the CPU is idle, priority has almost no effect.
package main;

public class Test1 {
    
    

    public static void main(String[] args) {
    
    
        Runnable task1 = () -> {
    
    
            int count = 0;
            for (; ; ) {
    
    
                System.out.println("---->1 " + count++);
            }
        };
        Runnable task2 = () -> {
    
    
            int count = 0;
            for (; ; ) {
    
    
                // Thread.yield();
                System.out.println(" ---->2 " + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");
//        t1.setPriority(Thread.MIN_PRIORITY);
//        t2.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
    }
}

TimeUnit sleeps

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test8")
public class Test8 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        log.debug("enter");
        TimeUnit.SECONDS.sleep(1);
        log.debug("end");
//        Thread.sleep(1000);
    }
}

4.3.2.1 Case:: Preventing 100% CPU usage

When the course address is not calculated using the CPU, do not waste the CPU by idling. At this time, you can use or to give up the use of the CPU to other programs.
while(true)yieldsleep

@Slf4j(topic = "c.Test8")
public class Test8 {
    
    
    public static void main(String[] args) {
    
    
        while (true) {
    
    
            try {
    
    
                Thread.sleep(50);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}
  • You can use waitor condition variables to achieve similar effects.
  • The difference is that the latter two require locking and corresponding wake-up operations, which are generally suitable for scenarios where synchronization is required.
  • sleepSuitable for scenarios where lock synchronization is not required.

4.3.3 Detailed explanation of join method

Why is the join method needed?

The following code is executed and what is printed r?

  static int r = 0;

    public static void main(String[] args) throws InterruptedException {
    
    
        test1();
    }

    private static void test1() throws InterruptedException {
    
    
        log.debug("开始");
        Thread t1 = new Thread(() -> {
    
    
            log.debug("开始");
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            log.debug("结束");
            r = 10;
        });
        t1.start();
        log.debug("结果为:{}", r);
        log.debug("结束");
    }

Insert image description here
analyze

  1. Because the main thread and thread t1 are executed in parallel, it takes 1 second for the t1 thread to calculate r=10
  2. The main thread will print the result of r at the beginning, so it can only print out r=0.

Solution

  1. Is it okay to use sleep? Why?
  2. Use join and add it after t1.start()

4.3.4 Detailed explanation of interrupt method

===>Interrupt the threads of sleep, wait, and join

The sleep, wait, and join methods will all put the thread into a blocking state.

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test11")
public class Test11 {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t1 = new Thread(() -> {
    
    
            log.debug("sleep...");
            try {
    
    
                Thread.sleep(5000); // wait, join
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"t1");

        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
        log.debug("打断标记:{}", t1.isInterrupted());
    }
}

Insert image description here

4.3.4.1 interrupt interrupt normal
import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test12")
public class Test12 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t1 = new Thread(() -> {
    
    
            while(true) {
    
    
                boolean interrupted = Thread.currentThread().isInterrupted();
                if(interrupted) {
    
    
                    log.debug("被打断了, 退出循环");
                    break;
                }
            }
        }, "t1");
        t1.start();

        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
    }
}
4.3.4.2 interrupt design pattern - terminated at both ends

Termination mode: Two Phase Termination

Goal: How to gracefully terminate thread T2 in thread T1? Elegance means giving T2 a post-processor

Wrong thinking:

  • Use the stop() method of the thread object to stop the thread: the stop method will actually kill the thread. If the thread locks the shared resource at this time, it will never have a chance to release the lock after it is killed, and other threads will never be able to acquire the lock.
  • Use the System.exit(int) method to stop a thread: the purpose is only to stop one thread, but this approach will stop the entire program

Illustration of the two-stage termination mode:
Insert image description here
The thread may be interrupted at any time, so you need to consider how to handle the interruption at any time:

public class Test {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        Thread.sleep(3500);
        tpt.stop();
    }
}
class TwoPhaseTermination {
    
    
    private Thread monitor;
    // 启动监控线程
    public void start() {
    
    
        monitor = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                while (true) {
    
    
                    Thread thread = Thread.currentThread();
                    if (thread.isInterrupted()) {
    
    
                        System.out.println("后置处理");
                        break;
                    }
                    try {
    
    
                        Thread.sleep(1000);					// 睡眠
                        System.out.println("执行监控记录");	// 在此被打断不会异常
                    } catch (InterruptedException e) {
    
    		// 在睡眠期间被打断,进入异常处理的逻辑
                        e.printStackTrace();
                        // 重新设置打断标记,打断 sleep 会清除打断状态
                        thread.interrupt();
                    }
                }
            }
        });
        monitor.start();
    }
    // 停止监控线程
    public void stop() {
    
    
        monitor.interrupt();
    }
}

Insert image description here

4.3.4.3 Interrupting park

Park functions like sleep, interrupting the park thread and not clearing the interrupt status (true)

import java.util.concurrent.locks.LockSupport;

public class Test2 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        Thread t1 = new Thread(() -> {
    
    
            System.out.println("park...");
            LockSupport.park();
            System.out.println("unpark...");
            System.out.println("打断状态:" + Thread.currentThread().isInterrupted());//打断状态:true
        }, "t1");
        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }
}
park...
unpark...
打断状态:true

If the interrupt flag is already true, park will fail

LockSupport.park();
System.out.println("unpark...");
LockSupport.park();//失效,不会阻塞
System.out.println("unpark...");//和上一个unpark同时执行

You can modify the method to obtain the interrupted status, use Thread.interrupted(), and clear the interrupted mark.

4.3.5 Not recommended methods

Methods are not recommended. These methods are obsolete and can easily destroy synchronized code blocks and cause thread deadlocks:

  • public final void stop(): Stop thread running

Reason for abandonment: The method is crude. Unless it is possible to execute the finally code block and release synchronized, the thread will be terminated directly. If the thread holds the JUC mutex lock, the lock may not be released in time, causing other threads to wait forever.

  • public final void suspend(): suspend (pause) thread running

Reason for deprecation: If the target thread holds a lock on a system resource while suspended, no thread can access the resource before the target thread resumes. If the thread that resumes the target thread attempts to access this shared resource before calling resume, it will cause death. Lock

  • public final void resume(): Resume thread running

4.3.6 Main thread and daemon thread

       By default, a Java process needs to wait for all threads to finish running before it ends. There is a special kind of thread called 守护线程, as long as other non-daemon threads finish running, even if the code of the daemon thread has not been executed, it will be forced to end.

	log.debug("开始运行...");
	
	Thread t1 = new Thread(() -> {
    
    
	log.debug("开始运行...");
	sleep(2);
	log.debug("运行结束...");
	}, "daemon");
	
	// 设置该线程为守护线程
	t1.setDaemon(true);
	t1.start();
	
	sleep(1);
	log.debug("运行结束...");
08:26:38.123 [main] c.TestDaemon - 开始运行... 
08:26:38.213 [daemon] c.TestDaemon - 开始运行... 
08:26:39.215 [main] c.TestDaemon - 运行结束...

Appendix 1

1. Java Concurrency Guide-Huang Xiaoxie
2. Others’ Notes
3. Others’ Notes 1

Appendix 2

Java monitoring and management platform jconsole

jconsoleRemote monitoring configuration

  • You need to run your java class as follows
java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -
Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -
Dcom.sun.management.jmxremote.authenticate=是否认证 java类
  • Modify the /etc/hosts file to map 127.0.0.1 to the host name

If you want to authenticate access, you need to do the following steps

  • Copy the jmxremote.password file
  • Modify the permissions of the jmxremote.password and jmxremote.access files to 600, which means the file owner can read and write
  • When connecting, fill in controlRole (username), R&D (password)

Insert image description here
Now set up a local connection on the Linux server and turn off the firewall
Insert image description here

Insert image description here

Insert image description here

Guess you like

Origin blog.csdn.net/Blue_Pepsi_Cola/article/details/132526326