java 线程池小结

来源:互联网 发布:数据分析是什么 编辑:程序博客网 时间:2024/05/18 01:32

在java中,使用线程来异步执行任务,java线程的创建和销毁都需要消耗一定的资源,因此从JDK5 开始,有了线程池这个好东西。

线程池(ThreadPoolExecutor)主要有以下几个好处:

(1)降低资源消耗:可以重复利用已创建的线程来降低线程创建和销毁造成的消耗;

(2)提高响应速度:当任务到达时,不需要等待线程创建就可以执行任务(因为线程已经在线程池里准备好了!);

(3)提高线程的可管理性:如果无限制的创建线程,不仅会消耗系统的资源,还会降低系统的稳定性,使用线程池可以对线程进行统一的分配,调优以及监控。

当一个任务交给我们的线程池时,线程池是如何处理的呢?

(1)线程池判断当前核心线程池是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务;否则进入步骤(2);

(2)线程池判断工作队列是否满了,如果工作队列还没有满,则进入工作队列,进行排队,否则进入步骤(3);

(3)线程池判断线程池内的所有线程是否都已经处于工作状态,如果不是,则创建一个新的线程来执行任务,否则进入步骤(4);

(4)采用饱和策略来处理此任务。

下面我们来看下任务交给线程池运行的方式:

一、调用 threadPoolExecutor.execute方法,此方法需要一个实现了Runnable接口的类,此方法不返回任何值

二、调用 threadPoolExecutor.submit方法,此方法需要一个实现Runnable接口或者Callable接口的类,并且此方法将返回一个执行结果,并且submit方法其实也还是调用了上面的execute方法,但是在其中加入了Fork、Join框架(JDK 1.7)(这里不展开讲,后续的博客将叙述)。

我们来看一下 threadPoolExecutor.execute(Runnable task)执行时都经历了哪一些事情:

1、如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(Waring:执行这一步骤,需要获得全局锁);

2、如果运行的线程等于或者多于corePoolSize,则将任务加到阻塞队列 BlockingQueue 中,进行排队操作;

3、如果无法加入BlockingQueue(队列已经满了),则尝试创建新的线程来处理任务(Waring:执行这一步骤,也需要获取全局锁);

4、尝试失败,即当前运行的线程数超过了maximumPoolSize,任务就会被拒绝,并采取相应的饱和策略。

具体的代码如下:

public void execute(Runnable command) {if (command == null)//任务必须不为null 否则抛异常throw new NullPointerException();int c = ctl.get();if (workerCountOf(c) < corePoolSize) {//当前工作线程数 小于 核心线程数if (addWorker(command, true))//1、尝试加入核心线程池执行任务(执行这一步骤,需要获得全局锁(使用可重入锁))return;//加入核心线程池成功, 则返回c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) {//2、尝试加入阻塞队列执行任务int recheck = ctl.get();//做一次double-check(1、加入阻塞队列 的时候没有获得全局锁,有可能存在不同步的情况;2、当前的threadPoolExecutor可能调用了shutdown或者shutdownNow方法)if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}//3、尝试加入阻塞队列失败(阻塞队列满了),则继续尝试新建一个线程执行任务(执行这一步骤,需要获得全局锁(使用可重入锁),与 maximumPoolSize 进行比较)else if (!addWorker(command, false))reject(command);//4、线程数超出maximumPoolSize 则采用饱和策略进行处理}

我们来看下手工new一个线程池:

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

corePoolSize:线程池的核心线程数,当一个任务提交到线程池后,线程池会创建一个线程来执行任务,即时当前有其他空闲线程,也会创建一个新线程,直到线程数大于
corePoolSize后将不再创建线程;
maximumPoolSize:线程池的最大线程数;
keepAliveTime:线程活动保持时间,线程空闲的时间,如果线程空闲时间超过该时间,线程将会回收;
TimeUnit:线程活动保持时间的单位;
BlockingQueue:阻塞队列类,后面给出说明;
ThreadFactory:线程工厂;
RejectedExecutionHandler:采用的饱和策略,当线程池和队列都满了,线程池将采取一定的饱和策略进行处理新提交的任务,默认是AbortPolicy。


详细来说一下BlockingQueue接口:

BlockingQueue接口的实现类主要有:

1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,此队列中的任务按照FIFO原则进行排序;

2、LinkedBlockingQueue:基于链表结构的无界阻塞队列(这里的无界是指默认情况下,也可以自己指定队列的大小),此队列中的任务按照FIFO原则进行排序;并且吞吐量往往要高于 ArrayBlockingQueue,Executors.newSingleThreadExecutor()和Executors.newFixedThreadPool(int n)这两个静态方法中使用了 此队列;

3、SynchronousQueue:内部不存储任何任务的阻塞队列,每次插入,都必须等待正在执行的任务完成,否则插入操作一直处于阻塞状态,吞吐量往往高于LinkedBlockingQueue,Executors.newCachedThreadPool()中使用了此队列;

4、PriorityBlockingQueue:具有优先级的无界阻塞队列;

5、...

关闭线程池:

shutdown()方法:将线程池状态置为SHOTDOWN状态,然后中断所有没有执行的线程;

shutdownNow()方法:将线程池状态置为STOP状态,然后中断所有没有执行的线程,并且尝试中断所有可以相应中断的线程。

RejectedExecutionHandler(饱和策略):

1、AboutPOlicy:默认使用此策略,直接抛出异常;

2、CallerRunsPolicy:只用调用者所在的线程来运行任务;

3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务;

4、DiscardPolicy:不处理,丢弃掉。

线程池如何配置:

核心线程配置:

1、Cpu密集型:核心线程数建议设置为当前CPU个数的N+1个,场景:复杂计算的场景,电脑的计算能力极强,,用完即可释放,所以可以设置的稍小些。;

2、IO密集型:核心线程数建议设置为当前CPU个数的N*2个,场景:比如导入,导出excel,单个线程占据CPU的时间会比较大,所以设置大些。

3、数据库交互密集型:核心线程数建议可以设置为很大(比如几百个),场景:频繁地与数据库进行交互,等待时间可能会久,则CPU空闲的时间越多;

阻塞队列配置:建议使用有界队列,有界队列可以增加系统的预警能力和稳定性,可以一次性设置的大一些,比如几千;原因:当队列和线程池全部满了以后,我们采用默认的饱和策略,直接回抛出异常,这个时候我们就可以根据异常进行排查原因了,而如果使用了无界队列,maxpoolSize、饱和策略都失效了,也就不可能进行抛异常的处理,一直在队列中堵塞着。

0 0
原创粉丝点击