1.为什么要使用线程池
在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,服务器应用程序需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利
用已有对象来进行服务,这就是“池化资源”技术产生的原因。
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。
2.线程池的组成部分
一个比较简单的线程池至少应包含线程池管理器、工作线程、任务列队、任务接口等部分。其中线程池管理器的作用是创建、销毁并管理线程池,将工作线程放入线程池中;工作线程是一个可以循环执行任务的线程,在没有任务是进行等待;任务列队的作用是提供一种缓冲机制,将没有处理的任务放在任务列队中;任务接口是每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执行状态等,工作线程通过该接口调度任务的执行。
线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务。
工作线程是一个可以循环执行任务的线程,在没有任务时将等待。
任务接口是为所有任务提供统一的接口,以便工作线程处理。任务接口主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。
3.线程池适合应用的场合
- 单个任务处理时间短
- 需要处理的任务数量大
一、Java自带线程池
先看看Java自带线程池的例子,开启5个线程打印字符串List:
package com.luo.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadTest {
public static void main(String[] args) {
List<String> strList = new ArrayList<String>();
for (int i = 0; i < 100; i++) {
strList.add("String" + i);
}
int threadNum = strList.size() < 5 ? strList.size() : 5;
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, threadNum, 300,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < threadNum; i++) {
executor.execute(new PrintStringThread(i,strList,threadNum));
}
executor.shutdown();
}
}
class PrintStringThread implements Runnable {
private int num;
private List<String> strList;
private int threadNum;
public PrintStringThread(int num, List<String> strList, int threadNum) {
this.num = num;
this.strList = strList;
this.threadNum = threadNum;
}
public void run() {
int length = 0;
for(String str : strList){
if (length % threadNum == num) {
System.out.println("线程编号:" + num + ",字符串:" + str);
}
length ++;
}
}
}
Java自带线程池构造方法
/**
* 创建一个线程池.
*
* @param corePoolSize 线程池长期维持的线程数,即使线程处于空闲状态,也不会回收
* @param maximumPoolSize 线程池维护线程的最大线程数
* @param keepAliveTime 线程池维护线程所允许的空闲时间,
超过corePoolSize的线程空闲时长超过这个设置的时间,多余的线程会被回收
* @param unit 程池维护线程所允许的空闲时间的单位
* @param workQueue 线程池所使用的缓冲队列
* @param handler 线程池对拒绝任务的处理策略
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
}
线程池任务执行描述
1.线程池启动初期:线程池在启动初期,线程并不会立即启动(poolSize=0),而是要等到有任务提交时才会启动,除非调用了prestartCoreThread(预启动一个空闲任务线程待命)或者prestartAllCoreThreads(预启动全部空闲任务线程待命,此时poolSize=corePoolSize)事先启动核心线程。
2.任务数量小于corePoolSize的情况:我们假设未进行线程预启动,那么每提交一个任务给线程池,线程池都会为其创建一个任务线程,直至所创建的线程数量达到corePoolSize(这些线程都是非空闲状态的线程,如果有空闲状态的线程,新提交的任务会直接分配给空闲任务线程去处理)。
3.任务数量大于corePoolSize且小于maximumPoolSize的情况:当线程池中工作线程数量达到corePoolSize时,线程池会把此时进入的任务提交给workQueue进行“排队等待”处理,如果此时恰好线程池内某个(或者某些)核心线程处于空闲状态(已处理完之前的任务),那么线程池会把任务从阻塞队列中取出,交给这个(些)空闲的线程去处理。如果此时workQueue已经满了,且工作核心线程数已经到poolSize=corePoolSize的状态,那么线程池就会继续创建新的线程来处理任务,但是工作线程总数不会超过maximumPoolSize。
4.任务数量超出maximumPoolSize的情况:当线程池接受的任务数量超出maximumPoolSize时,超出的任务会被线程池拒绝处理,我们称线程池已饱和,具体饱和策略需要参照handler。
注意:
当线程池中工作线程的数量超过corePoolSize时,超出的工作线程空闲时间达到keepAliveTime时,会关闭空闲线程。
当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程(核心线程)空闲时间达到keepAliveTime也将关闭
参考:https://blog.youkuaiyun.com/fxd873/article/details/88915583
https://www.cnblogs.com/sharpest/p/10578179.html