一,线程池的简介
线程池顾名思义,是专门用来放置线程的容器。避免了线程在代码运行期间的不断地创建以及销毁损耗时间,提高了系统的性能,也提高的执行效率。可能对于没有用过的人来说,线程池是比较难以上手或者理解的,其实并不是这样,当你揭开神秘的面纱时,线程池也就很好上手了。
二,线程池的基本参数以及使用
1,线程池的基本参数
线程池维护核心线程的最大数量:线程池里面最大只能一直存活的核心线程数
线程池维护的最大线程数量:这个最大数量和核心线程池最大数量不是一个概念,你可以这样理解(最大核心线程=<线程池最大线程数量)
非核心线程的最大存活时间:就是非核心线程在线程池的允许存活时间
线程池维护线程所允许的空闲时间的单位:这个就是时间单位,分,秒,...
注:还有其他核心的参数,后面会详细的介绍,这里就不占用篇幅了
2,线程池的用法
2.1 线程池的第一种阻塞队列:直接提交队列
线程池的创建,创建方式代码呈上。
/**
* 线程池的工具类
* <p>
* </p>
*
* @author libing
* @since 2019-07-18 上午 10:07
*/
@Slf4j
@Component
public class ThreadPoolUtils {
/**
* 线程池维护核心线程的最大数量
*/
public static final int CORE_POOL_SIZE = 5;
/**
* 线程池维护的最大线程数量
*/
public static final int MAX_IMUN_POOL_SIZE = 10;
/**
* 线程池维护线程所允许的空闲时间,当线程数大于核心线程时,此为终止前多余的空闲线程等待新任务的最长时间
*/
public static final long KEEP_ALIVE_TIME = 5;
/**
* 线程池维护线程所允许的空闲时间的单位
*/
private TimeUnit seconds = TimeUnit.SECONDS;
/**
* 线程池的第一种阻塞队列:直接提交队列
*/
final static SynchronousQueue<Runnable> synchronousQueue = new SynchronousQueue<Runnable>();
/**
* ThreadFactoryBuilder 使用这个符合阿里巴巴的开发手册.
*/
final static ThreadFactory buildFactory = new ThreadFactoryBuilder().setNameFormat("liBing_pool_%d").build();
/**
* 线程池的第一种任务队列:直接提交队列
* <p>
* 注:这种情况,如果线程创建数超过最大线程数,则后面的任务则会执行拒绝策略为AbortPolic策略,直接抛出异常
*/
public void synchronousQueue() {
//创建线程池并且设置相应的参数
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_IMUN_POOL_SIZE,
KEEP_ALIVE_TIME,
seconds,
synchronousQueue,
buildFactory,
abortPolicy);
for (int i = 1; i <= 11; i++) {
MyThread thread = new MyThread();
threadPoolExecutor.execute(thread);
System.out.println("线程池中现在的线程数目是:" + threadPoolExecutor.getPoolSize() + ", 队列中正在等待执行的任务数量为:" +
threadPoolExecutor.getQueue().size() + ",已经执行完的任务数量:" + threadPoolExecutor.getCompletedTaskCount());
}
}
}
这是第一种方式,首先线程池的创建基本格式就是这样。前四个参数很好理解,最大核心线程数,线程池最大线程数,非核心线程数的存活时间,存活时间的单位。真正核心的就是后面几个参数。
阻塞队列的直接提交队列。说到队列,那么我们就有必要说说,这个线程是怎么放进去的,数量又是如何控制的。打个不恰当的比方,放到线程池的你可以理解是一个个的待执行的任务,先进核心线程,达到最大的核心线程数量的时候,我们会将任务放在队列中,等队列满了,我们会将任务放到非核心线程中。简而言之,先核心,后队列,再非核心。直接提交队列可以理解为队列的size=0;
阻塞队列有四种
1,直接提交队列 :相当于没有队列。size =0
/**
* 线程池的第一种阻塞队列:直接提交队列
*/
final static SynchronousQueue<Runnable> synchronousQueue = new SynchronousQueue<Runnable>();
2,有界的任务队列,size是new出来的后面(10),这就是size=10,队列可以放10个线程任务
/**
* 线程池的第二种阻塞队列:有界的任务队列
*/
final static ArrayBlockingQueue<Runnable> arrayBlockingQueue= new ArrayBlockingQueue<Runnable>(10);
3,无界的任务队列,就是队列无限大,但是这个后面也是可以放size的,放了就是填写的size值(已验证)
/**
* 线程池的第三种阻塞队列:无界的任务队列
* 使用无界任务队列,线程池的任务队列可以无限制的添加新的任务
* 要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题
*/
final static LinkedBlockingQueue<Runnable> linkedBlockingQueue= new LinkedBlockingQueue<Runnable>(10);
final static LinkedBlockingQueue<Runnable> linkedBlockingQueue= new LinkedBlockingQueue<Runnable>();
4,线程池的第四种阻塞队列:优先级队列,就是队列里面会进行任务优先级的比较,优先级大先执行,前面几种但是先进先出的模式
/**
* 线程池的第四种阻塞队列:优先级队列
*/
final static PriorityBlockingQueue<Runnable> priorityBlockingQueue= new PriorityBlockingQueue<Runnable>();
现在你对于队列应该有一个大概的了解了吧。那么我们说下第六个参数(buildFactory)。这个参数创建线程池可以自定义命名线程池的名称,方便回溯问题,同时阿里巴巴开发手册一直强调建议使用这样的写法。代码上面有,我这里再贴一遍
/**
* ThreadFactoryBuilder 使用这个符合阿里巴巴的开发手册.
*/
final static ThreadFactory buildFactory = new ThreadFactoryBuilder().setNameFormat("liBing_pool_%d").build();
第七个参数(abortPolicy),这是默认的拒绝策略。核心线程数满了,队列满了,非核心线程也满了,还有线程任务怎么办,这个时候就会默认执行拒绝策略,后面的进来的线程任务全部不执行同时终止程序。
拒绝策略有四种:
1、AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
2、CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
3、DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
4、DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
测试代码:
/**
*
*线程池的第一种任务队列:直接提交队列
*
*/
@Test
public void synchronousQueue (){
threadPoolUtils.synchronousQueue();
log.info("验证程序是否正常运行");
}
另外说下,我这边测试的时候核心线程是5,线程池最大是10,队列是直接提交队列(就是所谓的size=0)
这个不能很明显的验证我们之前说的,先核心,后队列,再非核心的存储方式,下面会一个个贴出来跑的结果。
2.2 线程池的第二种阻塞队列:有界的任务队列
/**
* 线程池的第二种阻塞队列:有界的任务队列
*
*/
public void arrayBlockingQueue() {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_IMUN_POOL_SIZE,
KEEP_ALIVE_TIME,
seconds,
arrayBlockingQueue,
buildFactory,
abortPolicy);
for (int i = 1; i <= 33; i++) {
MyThread thread = new MyThread();
threadPoolExecutor.execute(thread);
System.out.println("线程池中现在的线程数目是:" + threadPoolExecutor.getPoolSize() + ", 队列中正在等待执行的任务数量为:" +
threadPoolExecutor.getQueue().size() + ",已经执行完的任务数量:" + threadPoolExecutor.getCompletedTaskCount());
}
try {
String name = Thread.currentThread().getName();
log.info("【{}】线程睡眠开始", name);
Thread.sleep(5000);
log.info("【{}】线程睡眠结束", name);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("循环结束,线程池中现在的线程数目是:" + threadPoolExecutor.getPoolSize() + ", 队列中正在等待执行的任务数量为:" +
threadPoolExecutor.getQueue().size() + ",已经执行完的任务数量:" + threadPoolExecutor.getCompletedTaskCount());
}
这里,同样我们验证临界值之外的情况。我这边测试的时候核心线程是5,线程池最大是10,队列是有界的任务队列(size=10)
/**
*
*线程池的第二种任务队列:直接提交队列
*
*/
@Test
public void arrayBlockingQueue (){
threadPoolUtils.arrayBlockingQueue();
log.info("验证程序是否正常运行");
}
看到这样的情况我想不用说了吧,控制台展示的很明显。先核心,后队列,再非核心的存储方式。
2.2 线程池的第三种阻塞队列:无界的任务队列
/**
* 线程池的第三种任务队列:无界的任务队列
* <p>
* 注:这种情况,如果线程创建数超过最大线程数,则后面的任务则会执行拒绝策略为AbortPolic策略,直接抛出异常
*/
public void linkedBlockingQueue() {
//创建线程池并且设置相应的参数
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_IMUN_POOL_SIZE,
KEEP_ALIVE_TIME,
seconds,
linkedBlockingQueue,
buildFactory,
abortPolicy);
for (int i = 1; i <= 100; i++) {
MyThread thread = new MyThread();
threadPoolExecutor.execute(thread);
System.out.println("线程池中现在的线程数目是:" + threadPoolExecutor.getPoolSize() + ", 队列中正在等待执行的任务数量为:" +
threadPoolExecutor.getQueue().size() + ",已经执行完的任务数量:" + threadPoolExecutor.getCompletedTaskCount());
}
}
测试代码
/**
*
*线程池的第三种任务队列:无界的任务队列
*
*/
@Test
public void linkedBlockingQueue (){
threadPoolUtils.linkedBlockingQueue();
log.info("验证程序是否正常运行");
}
不建议使用无界的,因为容易无限制消耗系统的内存,容易把系统整挂了。
2.2 线程池的第四种阻塞队列:优先级任务队列
/**
* 线程池的第四种任务队列:优先任务队列
* <p>
* 注:这种情况,如果线程创建数超过最大线程数,则后面的任务则会执行拒绝策略为AbortPolic策略,直接抛出异常
*/
public void priorityBlockingQueue() {
//创建线程池并且设置相应的参数
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_IMUN_POOL_SIZE,
KEEP_ALIVE_TIME,
seconds,
priorityBlockingQueue,
buildFactory,
abortPolicy);
for (int i=1;i<=10;i++) {
MyThread thread = new MyThread(i);
threadPoolExecutor.execute(thread);
System.out.println("线程池中现在的线程数目是:" + threadPoolExecutor.getPoolSize() + ", 队列中正在等待执行的任务数量为:" +
threadPoolExecutor.getQueue().size() + ",已经执行完的任务数量:" + threadPoolExecutor.getCompletedTaskCount());
}
}
package com.xiaoth.chengxiroad.robertcommon.thread;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.UUID;
/**
* 创建线程
* <p>
* </p>
*
* @author libing
* @since 2019-07-18 下午 5:12
*/
@Slf4j
@Data
public class MyThread implements Runnable,Comparable<MyThread> {
private int priority;
public MyThread() {
}
public MyThread(int priority) {
this.priority = priority;
}
//当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高
@Override
public int compareTo(MyThread o) {
log.info("this.priority:{},o.priority:{}",this.priority,o.priority);
System.out.println();
return this.priority>o.priority?-1:1;
}
@Override
public void run() {
try {
//让线程阻塞,使后续任务进入缓存队列
Thread.sleep(1000);
System.out.println("priority:"+this.priority+",ThreadName:"+Thread.currentThread().getName());
String name = Thread.currentThread().getName();
String sfasffas = name +"----"+ UUID.randomUUID();
String substring = sfasffas.substring(0, 15);
log.info("【{}】线程正在运行......",substring);
log.info("优先级:{}",this.getPriority());
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
*
*线程池的第四种任务队列:
*
*/
@Test
public void priorityBlockingQueue (){
threadPoolUtils.priorityBlockingQueue();
while (true){
}
}
核心线程1,最大线程池2,
这个里面第一个会先进核心线程,然后所有的线程会到队列中去,然后由队列进行优先级的排序,优先级值越高先执行,和之前的队列不一样(先进先出)。在源码进行了排序
源码比较时调用了我们重写的compareTo方法