Java多线程:线程池详解

Posted 流楚丶格念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程:线程池详解相关的知识,希望对你有一定的参考价值。

文章目录

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多线程:线程池详解的主要内容,如果未能解决你的问题,请参考以下文章

Java线程池详解

Java线程池详解

Java—线程池ThreadPoolExecutor详解

java线程池(详解)

Java线程池详解

java线程池