Java: Summary of content related to creating threads and thread pools


foreword

Recently, some problems and solutions related to Java threads have been summarized. Here is a sharing and summary.
Later, I will share some summaries about Java locks, Redis persistence and other related issues. I have summarized them from multiple documents and videos. I hope they are useful to everyone!


1. Four ways to create threads

Part of the content is referenced from: JUC multithreading: four ways to create threads

1. Inherit the Thread class

Thread implements the Runnable interface and represents an instance of a thread. The only way to start a thread is through the start() method of the Thread class. The start() method is a native method that starts a new thread and executes the run() method.

public class MyThread extends Thread {
    
    

	public static void main(String[] args){
    
    
		MyThread thread = new MyThread();
		thread.start();
	}
	@Override
	public void run() {
    
    
		System.out.println("MyThread.run()");
	}
}

Advantages: Simple, just inherit the Thread class
Disadvantages: Java is single inheritance, after inheriting the Thread class, you can no longer inherit other classes

Key points:
(1) The run() method is rewritten instead of the start() method!
(2) Java single inheritance is aimed at single inheritance between classes, and interface Interface can be multi-inherited

2. Implement the Runnable interface

Implement the run() method by implementing the Runnable interface, use the instance of the implementation class of the Runnable interface as the parameterized constructor of Thread, and start the thread by calling the start() method

public class MyThread implements Runnable {
    
    

	public static void main(String[] args){
    
    
		Thread thread = new Thread(new MyThread());
		thread.start();
	}
	
	public void run() {
    
    
		System.out.println("MyThread.run()");
	}
}

It can also be directly generated by anonymous inner class

public class MyThread {
    
    

	public static void main(String[] args){
    
    
		Thread thread = new Thread(new Runnable(){
    
    
			public void run(){
    
    
				System.out.println("Hello Runnable");
			}
		});
		thread.start();
	}
}

It can also be generated by means of lambda expressions

public class MyThread {
    
    

	public static void main(String[] args){
    
    
		Thread thread = new Thread(() -> System.out.println("Hello lambda"));
		thread.start();
	}
}

The use and detailed explanation of Lambda expressions: The use and detailed explanation of Lambda expressions in Java

3. Implement the Callable interface

(1) Implement the Callable interface and implement the call() method;
(2) Create an instance of the implementation class of the Callable interface, and wrap the Callable object with the FutureTask class, which encapsulates the return value of the call() method of the Callable object;
( 3) Use the FutureTask object as the target parameter of the constructor of the Thread class to create and start the thread;
(4) Call the get() of the FutureTask object to obtain the return value of the sub-thread execution end;

public class MyThread<String> implements Callable<String>{
    
    

	public static void main(String[] args) throws Exception{
    
    
		FutureTask<String> futureTask = new FutureTask<>(new MyThread());
		Thread thread = new Thread(futureTask);
		thread.start();
		String result = futureTask.get();
		System.out.println(result);
	}
	
    //重写call方法
    @Override
    public String call() {
    
    
        return "Hello Callable";
    }   
}

4. Thread pool

Use ThreadPoolExecutor to create a thread pool, and obtain threads from the thread pool to execute tasks. In JUC, the Executor framework has implemented several thread pools, and we will use Executor's newFixedThreadPool as a demo

public class MyThread implements Runnable {
    
    

	public static void main(String[] args) throws Exception{
    
    
		ExcutorService executorService = Excutors.newFixedTthreadPool(10);
		executor.execute(new MyThread);
	}
	
	public void run() {
    
    
		System.out.println("MyThread.run()");
	}
}

It is possible to implement the Callable or Runnable interface, and the thread is created by ExecutorService

Detailed explanation of thread pool content: ExecutorService detailed explanation

2. Thread pool status

1.RUNNING

Indicates that the thread pool is running normally, and can accept new tasks and process tasks in the queue normally

2.SHUTDOWN

When the shutdown() method of the thread pool is called, the thread pool enters the SHUTDWON state, indicating that the thread pool is in the closing state. In this state, the thread pool will not accept new tasks, but will continue to process the tasks in the queue

3.STOP

When the shutdownnow() method of the thread pool is called, the thread pool enters the STOP state, indicating that the thread pool is in the state of stopping. In this state, the thread pool will neither accept new tasks nor process tasks in the queue, and is running The thread of the

4.TIDYING

After there are no threads running in the thread pool, the state of the thread pool will automatically change to TIDYING, and terminated() will be called. This method is an empty method, which is left for the programmer to expand

5.TERMINATED

After the terminated() method is executed, the thread pool status will become TERMINATED

3. Why is it not recommended to use Executors to create thread pools?

To explain this problem, we can look at the source code of Excutors.newFixedTthreadPool():

//new LinkedBlockingQueue<Runnable>()这里可以看出 是声明的无界队列大小,默认大小为Integer.MAX_VALUE
 public static ExecutorService newFixedThreadPool(int nThreads) {
    
    
       return new ThreadPoolExecutor(nThreads, nThreads,
                                     0L, TimeUnit.MILLISECONDS,
                                     new LinkedBlockingQueue<Runnable>());
 }

The key to the problem is: LinkedBlockingQueue(), which is an unbounded blocking queue. If you use this thread pool to execute tasks, if there are too many tasks, they will be continuously added to the queue, and the more tasks, the more memory it will take up. It may cause the content to take up too much memory, resulting in OOM. Especially for large-scale and high-complexity systems, it is especially necessary to guard against this

When we use Executors to create SingleThreadExecutor, the corresponding construction method is:

//new LinkedBlockingQueue<Runnable>()这里可以看出 是声明的无界队列大小,默认大小为Integer.MAX_VALUE
 public static ExecutorService newSingleThreadExecutor() {
    
    
       return new FinalizableDelegatedExecutorService
       (new ThreadPoolExecutor(1, 1,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>()));
 }

The same LinkedBlockingQueue may also run out of memory

In addition to the possibility of OOM, using Executors to create a thread pool cannot customize the thread name, which is not conducive to troubleshooting, so it is recommended to directly use ThreadPoolExecutor to define the thread pool, which can be controlled more flexibly


Guess you like

Origin blog.csdn.net/qq_46119575/article/details/131212257