java线程池

1.线程池的核心概念

  线程池ThreadPoolExecutor的构造函数有多个参数,但是最重要的参数是workQueue阻塞队列这个参数。线程池正是使用了阻塞队列的特性,使得线程能够一直留在池中,而不会因为run方法执行完成而销毁。线程池的线程会从阻塞队列中取出任务执行,当阻塞队列为空时,阻塞队列会阻塞当前线程(挂起),直到有新的任务进入队列才会被唤醒。所以当目前没有任务时,线程池的线程会被挂起(不消耗cpu),线程就可以一直存在于进程中(只不过被挂起了而已)或者说存在于线程池中。这种方式体现了‘池’的概念,这些线程可以重复使用,有任务时被唤醒执行,没有任务则挂起,减少线程创建和销毁的性能消耗。
  1. corePoolSize:核心线程数,不要被这个参数的名称迷惑,其实这个参数实际上是线程池线程数。当线程池实际线程数量小于corePoolSize时,线程池将会创建新线程并加入到线程池中;大于corePoolSize时,会将任务加入队列,而不会继续创建线程,只有队列已经满的情况下,并且当前线程数小于maxPoolSize,才会创建新线程来执行任务。但是这部分线程会在keepAliveTime之后被销毁。
  2. maxPoolSize:最大线程数
  3. keepAliveTime:线程存活时间(在corePore<当前线程数<maxPoolSize情况下有用)
  4. timeUnit:存活时间的时间单位
  5. workQueue:阻塞队列(用来保存等待被执行的任务,关于workQueue参数的取值,JDK提供了4种阻塞队列类型供选择:)
    1. ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
    2. InkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务
    3. SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于ArrayBlockingQuene;
    4. PriorityBlockingQuene:具有优先级的无界阻塞队列;
  1. threadFactory:线程工厂,主要用来创建线程;
  2. handler:表示当拒绝处理任务时的策略,有以下四种取值
    1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
    2. ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
    3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 5.当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
 

2.例子

public class ThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,10,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
 
for (int i=0;i<15;i++){
poolExecutor.execute(new Task(i));
System.out.println("线程池中线程数目:"+poolExecutor.getPoolSize()+",队列中等待执行的任务数目:"+
poolExecutor.getQueue().size()+",已执行完成的任务数目:"+poolExecutor.getCompletedTaskCount());
}
for(;;) {
try {
//if (taskNum < 10) {
Thread.currentThread().sleep(3000);
//}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程池中线程数目:" + poolExecutor.getPoolSize() + ",队列中等待执行的任务数目:" +
poolExecutor.getQueue().size() + ",已执行完成的任务数目:" + poolExecutor.getCompletedTaskCount());
}
//poolExecutor.shutdown();
}
}
class Task implements Runnable {
private int taskNum;
 
public Task(int taskNum){
this.taskNum = taskNum;
}
 
 
@Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
//if (taskNum < 10) {
Thread.currentThread().sleep(4000);
//}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
输出:
正在执行task 0 线程池中线程数目:1,队列中等待执行的任务数目:0,已执行完成的任务数目:0 正在执行task 1 线程池中线程数目:2,队列中等待执行的任务数目:0,已执行完成的任务数目:0 线程池中线程数目:3,队列中等待执行的任务数目:0,已执行完成的任务数目:0 正在执行task 2 线程池中线程数目:4,队列中等待执行的任务数目:0,已执行完成的任务数目:0 正在执行task 3 线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完成的任务数目:0 正在执行task 4 线程池中线程数目:5,队列中等待执行的任务数目:1,已执行完成的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:2,已执行完成的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:3,已执行完成的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:4,已执行完成的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:5,已执行完成的任务数目:0 线程池中线程数目:6,队列中等待执行的任务数目:5,已执行完成的任务数目:0 正在执行task 10 线程池中线程数目:7,队列中等待执行的任务数目:5,已执行完成的任务数目:0 正在执行task 11 线程池中线程数目:8,队列中等待执行的任务数目:5,已执行完成的任务数目:0 正在执行task 12 线程池中线程数目:9,队列中等待执行的任务数目:5,已执行完成的任务数目:0 正在执行task 13 线程池中线程数目:10,队列中等待执行的任务数目:5,已执行完成的任务数目:0 正在执行task 14 线程池中线程数目:10,队列中等待执行的任务数目:5,已执行完成的任务数目:0 task 1执行完毕 task 3执行完毕 task 11执行完毕 task 10执行完毕 task 4执行完毕 task 2执行完毕 task 0执行完毕 task 14执行完毕 task 12执行完毕 task 13执行完毕 正在执行task 9 正在执行task 8 正在执行task 7 正在执行task 6 正在执行task 5 线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完成的任务数目:10 task 9执行完毕 task 6执行完毕 task 7执行完毕 task 8执行完毕 task 5执行完毕 线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完成的任务数目:15 线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完成的任务数目:15
可以看到线程池线程的数量会维持coreSize的数量,大于core和小于max的线程会被回收。
 

3.Executors工厂类

Executors是ThreadPoolExecutor线程池的工厂类,根据不同的入参创建了四种不同类型的线程池,下面一一列举这四种线程池的特性。
 
1.FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
它是一种固定线程数量的线程池;
在创建LinkedBlockingQueue阻塞队列时,没有指定容量。那么这就意味着该线程池永远都不会拒绝任务;
所以keepAliveTime和handler参数就是无效的。
 
2.SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
顾名思义,它是一种只有单个线程的线程池;
它与FixedThreadPool的区别只是在于coreThread的数量。
 
3.CachedThreadPool
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
它是一个可以无限扩大的线程池;
corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;
采用SynchronousQueue阻塞队列,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程;
因为SynchronousQueue阻塞队列的特性,它比较适合处理执行时间比较小的任务。
 
4.ScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
它用来处理延时任务或定时任务。
 
CachedThreadPool比较难以理解因此这里提供一个例子
package threadpool;

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

/**
*
* @author : LQ
* @since : 2019/1/18 14:50 Description:
*/
public class ThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(0,1000,60, TimeUnit.SECONDS,new SynchronousQueue());

for (int i=0;i<100;i++){
poolExecutor.execute(new Task(i));
}
for(;;) {
System.out.println("线程池中线程数目:" + poolExecutor.getPoolSize() + ",队列中等待执行的任务数目:" +
poolExecutor.getQueue().size() + ",已执行完成的任务数目:" + poolExecutor.getCompletedTaskCount());
}
//poolExecutor.shutdown();
}
}
class Task implements Runnable {
private int taskNum;

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


@Override
public void run() {
System.out.println("thread name:"+Thread.currentThread().getName());
}
}
输出:
线程池中线程数目:14,队列中等待执行的任务数目:0,已执行完成的任务数目:100
 
可以看到虽然提交了100个任务,但其实创建的线程只有7个(每次创建的线程数量都不一样)。如果任务的处理时间很长,那么就很可能会创建100个线程了。
原文地址:https://www.cnblogs.com/lanqi/p/11585159.html