java多线程:线程池详解(代码片段)

流楚丶格念 流楚丶格念     2022-11-29     367

关键词:

文章目录

1. 线程池

类比我们平常见到的水池,线程池也可以看做成一个池子,在该池子中存储很多个线程。

1.1 线程池概述

1.1.1 线程池的概念

系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗,这样就有点"舍本逐末"了。

针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,它们的集合称为线程池,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。

1.1.2 线程池的工作机制

在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程
一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务

1.1.3 使用线程池的原因

多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过度消耗系统资源,以及过渡切换线程的危险,从而导致系统资源的崩溃,这时,线程池也就是最好的选择了

1.1.3 线程池的设计思路 :

  1. 准备一个任务容器
  2. 一次性启动多个线程,每个线程都被称作消费者线程
  3. 刚开始任务容器是空的,所以线程都在wait
  4. 直到一个外部线程向这个任务容器中扔了一个"任务",就会有一个消费者线程被唤醒
  5. 这个消费者线程取出"任务",并且执行这个任务,执行完毕后,继续等待下一次任务的到来

1.2 线程池的创建

1.2.1 Executors默认线程池

JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池。

我们可以使用Executors中所提供的静态方法来创建线程池

Executors提供了下面两种方法来创建线程池

static ExecutorService newCachedThreadPool()   创建一个默认的线程池
​static newFixedThreadPool(int nThreads)	    创建一个指定最多线程数量的线程池
newSingleThreadExecutor()	创建只有一个线程的线程池

newCachedThreadPool:创建默认线程池,最多容纳int类型的最大值

使用Executors中所提供的静态方法来创建线程池

static ExecutorService newCachedThreadPool()   创建一个默认的线程池

代码实现 :

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

public class MyThreadPoolDemo 
    public static void main(String[] args) throws InterruptedException 

        //1,创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Executors --- 可以帮助我们创建线程池对象
        //ExecutorService --- 可以帮助我们控制线程池

        executorService.submit(()->
            new Thread(()->
                System.out.println(Thread.currentThread().getName() + "在执行了");
            ).start();
        );

        //Thread.sleep(2000);

        executorService.submit(()->
            new Thread(()->
                System.out.println(Thread.currentThread().getName() + "在执行了");
            ).start();
        );

        executorService.shutdown();
    

运行结果如下:

newFixedThreadPool:创建指定上限的线程池

使用Executors中所提供的静态方法来创建线程池

static ExecutorService newFixedThreadPool(int nThreads) : 创建一个指定最多线程数量的线程池

代码实现 :

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

public class MyThreadPoolDemo2 
    public static void main(String[] args) throws InterruptedException 
        //参数不是初始值而是最大值
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;

        // 没有线程进入线程池呢还
        System.out.println(pool.getPoolSize());//0

        executorService.submit(()->
            System.out.println(Thread.currentThread().getName() + "在执行了");
        );

        executorService.submit(()->
            System.out.println(Thread.currentThread().getName() + "在执行了");
        );


        Thread.sleep(1000);
        System.out.println(pool.getPoolSize());//2
        System.out.println(pool.getActiveCount());//0

        System.out.println("关闭线程池");
        executorService.shutdown();
        System.out.println(pool.getPoolSize());//0
    

从结果我们可以看出:当没有线程进入线程池时,即使设置了最大容量,线程池内大小也是0,因为没有线程进入线程池,

当两条线程进入线程池运行完后,线程池的大小就变为了2,里面有两条线程,但是活跃的线程为0,

关闭线程池后线程池的大小就为0了。

newSingleThreadExecutor:创建只有一个线程的线程池

class MyThreadPoolDemo4 
    public static void main(String[] args) 
        // newSingleThreadExecutor
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(()->
            System.out.println(Thread.currentThread().getName() + "在执行了");
        );

        executorService.submit(()->
            System.out.println(Thread.currentThread().getName() + "在执行了");
        );
    

1.3 ThreadPoolExecutor

Executors中创建线程池的快捷方法,实际上是调用了ThreadPoolExecutor的构造方法(定时任务使用的是ScheduledThreadPoolExecutor),该类构造方法参数列表如下:

// Java线程池的完整构造函数
public ThreadPoolExecutor(
  int corePoolSize, 		// 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。
  int maximumPoolSize, 		// 线程数的上限
  long keepAliveTime, TimeUnit unit, // 超过corePoolSize的线程的idle时长,
                                     // 超过这个时间,多余的线程会被回收。
  BlockingQueue<Runnable> workQueue, // 任务的排队队列
  ThreadFactory threadFactory, // 新线程的产生方式
  RejectedExecutionHandler handler	// 拒绝策略
) 

1.3.1 创建线程池对象 :

通过ThreadPoolExecutor创建线程池对象:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

具体代码实现 :

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

class MyRunnable implements Runnable 
    @Override
    public void run() 
        System.out.println(Thread.currentThread().getName() + "在执行了");
    


public class MyThreadPoolDemo3 
	//    参数一:核心线程数量
	//    参数二:最大线程数
	//    参数三:空闲线程最大存活时间
	//    参数四:时间单位
	//    参数五:任务队列
	//    参数六:创建线程工厂
	//    参数七:任务的拒绝策略
    public static void main(String[] args) 
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        pool.shutdown();
    


运行结果如下:

如果是玩命加线程的话,这里的拒绝策略是抛出异常,那么他就是抛出异常

1.3.2 参数详解

参数的取值范围如下:

参数取值范围
核心线程数量不能小于0
最大线程数不能小于等于0,最大数量>=核心线程数量
空闲线程最大存活时间不能小于0
时间单位时间单位
任务队列不能为null
创建线程工厂不能为null
任务的拒绝策略不能为null

构造函数参数对照:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
    
corePoolSize:   核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime:  空闲线程最大存活时间,不能小于0
unit:           时间单位
workQueue:      任务队列,不能为null
threadFactory:  创建线程工厂,不能为null      
handler:        任务的拒绝策略,不能为null  

1.3.3 非默认任务拒绝策略

RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。

拒绝策略说明
ThreadPoolExecutor.AbortPolicy丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy调用任务的run()方法绕过线程池直接执行。

注:明确线程池对多可执行的任务数 = 队列容量 + 最大线程数

1.3.4 四种任务策略代码示例

ThreadPoolExecutor.AbortPolicy

ThreadPoolExecutor.AbortPolicy任务处理策略:丢弃任务并抛出RejectedExecutionException异常。是默认的策略。

public class ThreadPoolExecutorDemo01 
    public static void main(String[] args) 

        /* 
        	核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
       	*/
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;

        // 提交5个任务,而该线程池最多可以处理4个任务,当我们使用AbortPolicy这个任务处理策略的时候,就会抛出异常
        for(int x = 0 ; x < 5 ; x++) 
            threadPoolExecutor.submit(() -> 
                System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
            );
        
    

控制台输出结果

控制台报错,仅仅执行了4个任务,有一个任务被丢弃了

ThreadPoolExecutor.DiscardPolicy

ThreadPoolExecutor.DiscardPolicy任务处理策略:丢弃任务,但是不抛出异常 这是不推荐的做法。

public class ThreadPoolExecutorDemo02 
    public static void main(String[] args) 
        /* 
        	核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
         */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;

        // 提交5个任务,而该线程池最多可以处理4个任务,当我们使用DiscardPolicy这个任务处理策略的时候,控制台不会报错
        for(int x = 0 ; x < 5 ; x++) 
            threadPoolExecutor.submit(() -> 
                System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
            );
        
    

控制台输出结果

控制台没有报错,仅仅执行了4个任务,有一个任务被丢弃了

ThreadPoolExecutor.DiscardOldestPolicy

ThreadPoolExecutor.DiscardOldestPolicy任务处理策略:抛弃队列中等待最久的任务 然后把当前任务加入队列中。

public class ThreadPoolExecutorDemo02 
    public static void main(String[] args) 
        /* 
        	核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
         */
        ThreadPoolExecutor threadPoolExecutor;
        threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardOldestPolicy());
        // 提交5个任务
        for(int x = 0 ; x < 5 ; x++) 
            // 定义一个变量,来指定指定当前执行的任务;这个变量需要被final修饰
            final int y = x ;
            threadPoolExecutor.submit(() -> 
                System.out.println(Thread.currentThread().getName() + "---->> 执行了任务" + y);
            );     
        
    

控制台输出结果

由于任务1在线程池中等待时间最长,因此任务1被丢弃。

ThreadPoolExecutor.CallerRunsPolicy

ThreadPoolExecutor.CallerRunsPolicy任务处理策略:调用任务的run()方法绕过线程池直接执行。

class ThreadPoolExecutorDemo04 
    public static void main(String[] args) 

        /*
            核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
        */
        ThreadPoolExecutor threadPoolExecutor;
        threadPoolExecutor = new ThreadPoolExecutor(1,
                3, 20, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());

        // 提交5个任务
        for (int x = 0; x < 5; x++) 
            threadPoolExecutor.submit(() -> 
                System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
            );
        
    

控制台输出结果

通过控制台的输出,我们可以看到次策略没有通过线程池中的线程执行任务,而是直接调用任务的run()方法绕过线程池直接执行。

java多线程:线程池详解(代码片段)

目录1.什么是线程池2.为什么要使用线程池3.线程池创建3.1 固定数量的线程池(Executors.newFixedThreadPool) 3.1.1创建固定数量的线程池3.1.2 线程池返回结果3.1.3submit()VSexecut()3.2.4线程工厂3.2 带缓存的线程池(Executors.newCac... 查看详情

java多线程-----线程池详解(代码片段)

  1.线程池的实现原理   提交一个任务到线程池中,线程池的处理流程如下:判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执... 查看详情

java多线程:线程池详解(代码片段)

文章目录线程池注意点:线程池都有哪些状态?谈谈线程池的拒绝策略?线程池的队列大小通常怎么设置?1.CPU密集型任务2.IO密集型任务3.混合型任务线程池有哪些参数,各个参数的作用是什么?线程池注意点:... 查看详情

java—线程池threadpoolexecutor详解(代码片段)

引导要求:线程资源必须通过线程池提供,不允许在应用自行显式创建线程;说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建... 查看详情

java线程池详解(代码片段)

构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段。基础知识Executors创建线程池Java中创建线程池很简单,... 查看详情

java线程池详解(代码片段)

构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段。基础知识Executors创建线程池Java中创建线程池很简单,... 查看详情

java—线程池threadpoolexecutor案例详解,高薪必备(代码片段)

引导要求:线程资源必须通过线程池提供,不允许在应用自行显式创建线程; 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统... 查看详情

java多线程(工具篇)(代码片段)

线程池原理线程池的七大参数详解:intcorePoolSize:该线程池中核心线程数最大值。核心线程:线程池中有两类线程,核心线程和非核心线程。核心线程默认情况下会一直存在于线程池中,即使这个核心线程什... 查看详情

java线程池详解(代码片段)

Java中的线程池(ThreadPoolExecutor)我们都知道(不知道请自行搜索),它的执行机制简单讲就是多个线程不停的从队列里面取任务执行。但是我们可能遇到下面这样的场景:我有一批数据要通过线程池来处理,处理过程中需要调... 查看详情

java线程池详解(代码片段)

Java中的线程池(ThreadPoolExecutor)我们都知道(不知道请自行搜索),它的执行机制简单讲就是多个线程不停的从队列里面取任务执行。但是我们可能遇到下面这样的场景:我有一批数据要通过线程池来处理,处理过程中需要调... 查看详情

java线程池详解(代码片段)

一、线程池有哪些优点减少资源开销,不用频繁的创建和销毁线程提高响应时长,有任务时可直接执行。提高线程的可管理性,所有线程资源都由线程池统一管理。二、线程池的主要参数线程池ThreadPoolExecutor的继承关... 查看详情

java线程池详解(代码片段)

一、线程池初探所谓线程池,就是将多个线程放在一个池子里面(所谓池化技术),然后需要线程的时候不是创建一个线程,而是从线程池里面获取一个可用的线程,然后执行我们的任务。线程池的关键在于它为我们管理了多个... 查看详情

02java进阶--线程池详解(代码片段)

线程池线程池做的工作主要是控制运行的线程的数量,处理过程中将任务加入队列,然后在线程创建后,启动这些任务,如果线程超过了最大数量,超出的数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行... 查看详情

java线程池threadpoolexecutor类使用详解(代码片段)

在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了... 查看详情

java线程池threadpoolexecutor类使用详解(代码片段)

在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了... 查看详情

java常用四大线程池用法以及threadpoolexecutor详解(代码片段)

为什么用线程池?1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处-理效率2.线程并发数量过多,抢占系统资源从而导致阻塞3.对线程进行一些简单的管理在Java中,线程池的概念是Executor这个接口... 查看详情

java线程池executor框架详解(代码片段)

Java的线程既是工作单元,也是执行机制。从JDK5开始,把工作单元与执行机制分离开来。工作单元包括Runnable和Callable,而执行机制由Executor框架提供。Executor框架简介在HotSpotVM的线程模型中,Java线程(java.lang.Thread)被一对一映射... 查看详情

java线程池使用详解(代码片段)

上一篇:Java中线程安全的集合线程池线程池也是JUC包中提供的一个类,为啥要有线程池呢。举一个例子:程序运行期间,会有100个任务同时要执行,按照之前的写法那我们就要创建100个线程,运行完毕后&#... 查看详情