Java线程池

一.线程池的引入

1.存在的问题

在多线程并发的环境下,由于系统不能确定任意时刻线程的并发数量,如果每一次需要执行任务的时候都创建新的线程,然后用完立刻销毁的话,这对系统而言是一笔巨大的资源开销甚至导致资源耗尽的风险。

2.线程池的概念与优势

概念:线程池采用池化的思想,预先创建一定数量的线程并将线程资源集中到一起,统一监督,统一调配。类似的设计还包括数据库连接池等。
优势:线程池通过对资源的统一调配,降低了系统的资源损耗,提高了系统的响应速度。尤其在并发量巨大,同时单个线程任务时间又较短的情况下优势特别显著。

二.Java线程池的执行机制

1.总体概述

在java当中线程池的核心实现类是ThreadPoolExecutor:,实现了AbstractExecutorService抽象类:
在这里插入图片描述
顶层接口Executor将任务与线程进行了解耦,开发者只需要将包含业务逻辑的Runnable对象传入Executor中,由Executor框架来实现线程的调用与任务的执行。当工作线程找不到任务可以执行的时候,就会被JVM自动回收。
核心线程不会被JVM自动回收。因为线程池自身状态的维护也需要保证有一定数目的线程可供调用。

2.自身状态的维护

java线程池有两个重要的状态参数:运行状态(runState)和线程数量(workerCount)。

两个变量都存储在AtomicInteger原子变量ctl中,高3位保存runState,低29位保存workerCount。那么这也就意味着线程数量最多有:
在这里插入图片描述
即是源码里的COUNT_MASK变量:
在这里插入图片描述
运行状态共有五种:
在这里插入图片描述
状态转换流程:
在这里插入图片描述
执行shutdown()方法之后,线程池不再接受新的任务,但仍然会从阻塞队列中获取任务。因此,只有当阻塞队列为空并且工作线程为0时才会进入TIDYING状态。
常用

3.任务的执行

ThreadPoolExecutor执行任务的流程如图所示:
在这里插入图片描述
如果当前线程数未达到核心线程数,则立即调用线程执行该任务;如果已经达到核心线程数且阻塞队列未满,则添加任务到阻塞队列等待线程调用;否则判断当前线程数是否已经达到最大线程数,如果未达到,则添加线程执行任务;如果达到,则执行拒绝策略(抛出异常等)
四种拒绝策略如下

AbortPolicy(抛出异常同时拒绝执行该任务,默认此种拒绝策略)
DiscardPolicy(直接丢弃任务但不抛异常)
DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
CallerRunsPolicy(交有提交任务的线程去执行,适用于所有任务都需要执行的情况)

4.线程的调度

工作线程Worker实现了Runnable接口,并含有firstTask变量。该变量用于指明线程创建后需要执行的第一个任务,如果该变量为空,即对应着核心线程的创建

Worker线程的回收首先是通过AQS独占锁不可重入的特性来判断线程的工作状态,再来决定对Hash引用表的添加和移除等操作。

Worker线程执行任务的流程:
在这里插入图片描述

三.ThreadPoolExecutor构造方法

public ThreadPoolExecutor(
                 int corePoolSize, 
                 int maximumPoolSize, 
                 long keepAliveTime, 
                 TimeUnit unit, 
                 BlockingQueue<Runnable> workQueue, 
                 ThreadFactory threadFactory, 
                 RejectedExecutionHandler handler) 

另外还有三种构造方法:
在这里插入图片描述

各个参数的意义:

corePoolSize: 线程池核心线程数
maximumPoolSize: 线程池最大线程数
keepAliveTime: 线程池中非核心线程的空闲存活时间
unit: 线程空闲存活时间单位
workQueue: 存放任务的阻塞队列
threadFactory: 设置创建线程的工厂(自定义线程工厂的名称)
handler: 线程池的拒绝策略

四.手动创建线程池

阿里代码规约不允许使用Executors的静态方法直接创建线程池:
在这里插入图片描述
实例代码:

package package03thread;

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

/**
 * @author 自定义线程池并传入执行对象
 */
public class ThreadPoolExecutorTest {
    /**设置阻塞队列深度**/
    private static int queueDeep = 2;

    /**获取队列当前深度**/
    private synchronized int getQueueSize(Queue queue) {
        return queue.size();
    }

    /**开发者传入的业务对象**/
    private class Task implements Runnable {
        private int count;

        public Task(int count) {
            this.count = count;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread()+"执行第"+count+"个任务");
            try {
                //模拟线程完成任务需要的时间
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**创建线程池**/
    private void createThreadPool() {
        /**
         * 核心线程数为3,最大线程数为6,非核心线程最大空闲时间为2秒,使用深度为2的ArrayBlockingQueue队列
         */
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(3,6,2, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueDeep));
        //添加任务
        for (int i = 1; i <= 10; i++) {
            //阻塞队列已满
            while (getQueueSize(tpe.getQueue()) >= queueDeep) {
                System.out.println("队列已满,3秒后将再次尝试添加任务");
                try {
                    //提交任务的线程休眠3秒,等待核心线程或阻塞队列空出位置
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Task t = new Task(i);
            System.out.println("put:"+i);
            tpe.execute(t);
        }
        tpe.shutdown();
    }

    public static void main(String[] args) {
        ThreadPoolExecutorTest test = new ThreadPoolExecutorTest();
        test.createThreadPool();
    }
}

在这里插入图片描述
:当线程不够的时候,这里采取的是让线程休眠等待其余线程的空闲,并不是通过创建新的线程。

自定义线程池的关键之一是根据业务需求设定最适合的线程数。这一般是根据CPU的计算量和IO操作的次数共同决定的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值