线程池的优势
线程池做的工作主要是:控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
线程池的主要特点:线程复用,控制最大并发数,管理线程。
第一:降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
第三:提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控。
使用Executors 工具类来创建线程池的三大方法
方法一:ExecutorService threadPool = Executors.newSingleThreadExecutor();只有单个线程方法二:ExecutorService threadPool = Executors.newFixedThreadPool(5); 创建一个固定的线程池大小方法三:ExecutorService threadPool = Executors.newCachedThreadPool(); 执行很多短期异步任务,线程池根据需要创建新线程,但在先构建的线程可用时将重用他们。可扩容,遇强则强
代码示例
import java.util.concurrent.*;
// Executors 工具类 三大方法
public class Demo01 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 方法一:单个线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5); // 方法二:创建一个固定的线程池大小
ExecutorService threadPool = Executors.newCachedThreadPool(); // 方法三:可伸缩的,遇强则强,遇弱则弱
try {
for (int i = 0; i < 10; i++) {
// 使用了线程池后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "ok");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
结果:
方法一:
只有一个线程
方法二:
只能调到五个线程
方法三:
十个线程都有机会调到
但是在Java并发开发中,有这样的约束,阿里巴巴java开发文档中也提出了
讲七大参数之前,我们分析一下三大方法的源码:
方法一:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new linkedBlockingQueue()));
}
方法二:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new linkedBlockingQueue());
}
方法三:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, // 最大为21亿,所以可能导致OOM
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
可以清楚的看到三大方法的核心都是ThreadPoolExecutor()
所以我们看一下ThreadPoolExecutor()的源码
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
我们可以看到七大参数了,其中
int corePoolSize:核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。int maximumPoolSize:最大线程数。表明线程中最多能够创建的线程数量,此值必须大于等于1。long keepAliveTime:空闲的线程保留的时间。TimeUnit unit:空闲线程的保留时间单位
TimeUnit.DAYS; //天 TimeUnit.HOURS; //小时 TimeUnit.MINUTES; //分钟 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //纳秒
BlockingQueue 这就是四大拒绝策略: ThreadPoolExecutor 底层工作原理 在创建了线程池后,开始等待请求。 当调用execute()方法添加一个请求任务时,线程池会做出如下判断: 当一个线程完成任务时,它会从队列中取下一个任务来执行。 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断: 代码示例: 线程池的拒绝策略: 代码测试: 思考题:线程是否越多越好 一个计算为主的程序(专业一点称为CPU密集型程序)。多线程跑的时候,可以充分利用起所有的cpu 但是如果线程远远超出cpu核心数量 反而会使得任务效率下降,因为频繁的切换线程也是要消耗时间 因此对于cpu密集型的任务来说,线程数等于cpu数是最好的了。 如果是一个磁盘或网络为主的程序(IO密集型)。一个线程处在IO等待的时候,另一个线程还可以在 此时 线程数等于IO任务数是最佳的。ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
3、四大拒绝策略
举例:8个人进银行办理业务
1、1~2人被受理(核心大小core)
2、3~5人进入队列(Queue)
3、6~8人到最大线程池(扩容大小max)
4、再有人进来就要被拒绝策略接受了(也就是线程数>max+Queue时,触发拒绝策略)
过程:
1). 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务:
2). 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列:
3). 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非
核心线程立刻运行这个任务;
4). 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
1).如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉
2).所有线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务,抛出异常
rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务,不抛出异常【如 果允许任务丢失这是最好的】
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务 删,之后再尝试加入队列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么 主线程会自己去执行该任务,回退
public class Demo02 {
public static void main(String[] args) {
//获取CPU的核数,我电脑为8
System.out.println(Runtime.getRuntime().availableProcessors());
//自定义线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
Runtime.getRuntime().availableProcessors(),
3,
TimeUnit.SECONDS,
new linkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());// 队列满了,尝试和第一个竞争,也不会抛出异常
try {
// 模拟有9,10,11,12,13个顾客过来银行办理业务,观察结果情况
// 最大容量为:maximumPoolSize + workQueue = 最大容量数,一旦超过,就调用拒绝策略
for (int i = 1; i <= 20 ; i++) {
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
//线程池用完,程序结束,关闭线程池
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
核心,比如说4个核心的cpu,开4个线程的时候,可以同时跑4个线程的运算任务,此时是最大效率。
的。
CPU里面跑,有时候CPU闲着没事干,所有的线程都在等着IO,这时候他们就是同时的了,而单线程的
话此时还是在一个一个等待的。我们都知道IO的速度比起CPU来是慢到令人发指的。所以开多线程,比
方说多线程网络传输,多线程往不同的目录写文件,等等。



