【多线程】线程池

业界参考:

Java线程池实现原理及其在美团业务中的实践:  https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

Java线程池的实现原理及其在业务中的最佳实践  https://new.qq.com/rain/a/20240521A014JT00 


 

1.什么是线程池?

线程池是一种用于管理和复用线程的机制。线程池的核心思想是预先创建一定数量的线程,并把它们保存在线程池中,当有任务需要执行时,线程池会从空闲线程中取出一个线程来执行该任务。任务执行完毕后,线程不是被销毁,而是返还给线程池,可以立即或稍后被再次用来执行其他任务。这种机制可以避免因频繁创建和销毁线程而带来的性能开销,同时也能控制同时运行的线程数量,从而提高系统的性能和资源利用率。线程池的主要组成部分包括工作线程、任务队列、线程管理器等。线程池的设计有助于优化多线程程序的性能和资源利用,同时简化了线程的管理和复用的复杂性。

2.线程池有什么好处?

  • 减少线程创建和销毁的开销,线程的创建和销毁需要消耗系统资源,线程池通过复用线程,避免了对资源的频繁操作,从而提高系统性能;

  • 控制和优化系统资源利用,线程池通过控制线程的数量,可以尽可能地压榨机器性能,提高系统资源利用率;

  • 提高响应速度,线程池可以预先创建线程且通过多线程并发处理任务,提升任务的响应速度及系统的并发性能;

3. 线程池的问题:

  • CPU资源大量浪费在阻塞等待上,导致CPU资源利用率低。在Java 8之前,一般会通过回调的方式来减少阻塞,但是大量使用回调,又引发臭名昭著的回调地狱问题,导致代码可读性和可维护性大大降低。
  • 随着CPU调度线程数的增加,会导致更严重的资源争用,宝贵的CPU资源被损耗在上下文切换上,而且线程本身也会占用系统资源,且不能无限增加。

同步模型下,会导致硬件资源无法充分利用,系统吞吐量容易达到瓶颈。


Java线程池的核心实现类是ThreadPoolExecutor,其类继承关系如图所示

 

 

 

一、线程池创建

Executor:接口,仅execute接口

  ExecutorService extends Executor接口,并新定义shutdown、shuwdownNow、awaitTermination、submit、invokeAll等接口

    ScheduledExecutorService extends ExecutorService接口,新定义schedule/scheduleAtFixedRate/scheduleWithFixedDelay接口,均返回 ScheduledFuture

    AbstractExecutorService  extends ExecutorService:抽象类,实现了submit、invoke等方法

      ThreadPoolExecutor类:extends AbstractExecutorService,实现了shutdown等方法

ScheduledThreadPoolExecutor  extends ThreadPoolExecutor  implements ScheduledExecutorService 


 

Executors工厂类,生成各种线程池Executor(定时和非定时),返回ExecutorService

  • newCachedThreadPool

    创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。

    

  • newFixedThreadPool

    创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在

  • newScheduledThreadPool

    创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

              更多:newSingleThreadScheduledExecutor,

              内部核心实现:ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService

           

  • newSingleThreadExecutor

    返回一个线程池(这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去 !

 

 


二、线程池的关闭 

shutdown(): 已添加的任务会继续执行完毕,但是不会阻塞;新任务无法继续添加

  • Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. Invocation has no additional effect if already shut down.

    This method does not wait for previously submitted tasks to complete execution. Use awaitTermination to do that.

public List<Runnable> shutdownNow(): 在调用shutdownNow()方法后,线程池会立即关闭,未执行的任务会以列表的形式返回。

  Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution. These tasks are drained (removed) from the task queue upon return from this method.

  This method does not wait for actively executing tasks to terminate. Use awaitTermination to do that.

  There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. This implementation cancels tasks via Thread.interrupt(), so any task that fails to respond to interrupts may never terminate.

awaitTermination

  


 shutdownNow测试

  

shutdown: 原有任务继续执行完毕,新任务无法继续执行

      

 优雅退出  awaitTermination + shutdownnow

 private void shutdownAndAwaitTermination(ThreadPoolExecutor pool) {
        pool.shutdown(); // Disable new tasks from being submitted

        try {
            // Wait a while for existing tasks to terminate
            if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
                pool.shutdownNow(); // Cancel currently executing tasks

                // Wait a while for tasks to respond to being cancelled
                if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
                    Tracer.error("Pool did not terminate.");
                }
            }
        } catch (InterruptedException ie) {
            // (Re-)Cancel if current thread also interrupted
            pool.shutdownNow();

            Tracer.error("Current thread interrupt.");
            Tracer.error(ie.getMessage(), ie);
        }
    }
View Code

 

使用退出标志退出线程 (翻译自jdk.chm)

 方法:isInterrupted(): 返回当前中断标记

   方法:interrupt():interrupt()的作用是中断本线程。处理结果区分当前业务线程的状态

  本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。
  如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。

  如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。
  如果不属于前面所说的情况(比如正常的业务处理),那么通过interrupt()中断线程时,它的中断标记会被设置为“true”
  
中断一个“已终止的线程”不会产生任何操作。

 1 public class Main {
 2     public static void main(String[] args) {
 3         TestThread testThread = new TestThread();
 4         testThread.start();
 5 
 6         try {
 7             Thread.sleep(300);
 8         } catch (InterruptedException e) {
 9             e.printStackTrace();
10         }
11         testThread.interrupt();  //  此时业务线程正在sleep状态,所以最终打印 TestRunner is interrupted by InterruptedException; 
12     }
13 }
14 
15 public class TestThread extends Thread {
16     @Override
17     public void run() {
18         try {
19             while (!isInterrupted()) {
20                 Thread.sleep(2000L);
21                 for (int i = 0; i < 100000; i++) {
22 
23                 }
24             }
25             System.out.println("TestRunner exited by isInterrupted flag");  // 如果业务在执行for循环时被中断,则标记位=true,所以while循环退出,最终打印左侧语句
26 
27         } catch (InterruptedException e) {
28             System.out.println("TestRunner is interrupted by InterruptedException");
29         }
30     }
31 }
View Code

 

posted @ 2021-04-05 11:58  飞翔在天  阅读(106)  评论(0编辑  收藏  举报