非常适合新手学生的Java线程池超详细分析

网友投稿 293 2022-08-19


非常适合新手学生的Java线程池超详细分析

目录线程池的好处创建线程池的五种方式缓存线程池CachedThreadPool固定容量线程池FixedThreadPool单个线程池SingleThreadExecutor定时任务线程池ScheduledThreadPoolThreadPoolExecutor创建线程池(十分推荐)ThreadPoolExecutor的七个参数详解workQueuehandler如何触发拒绝策略和线程池扩容?

线程池的好处

可以实现线程的复用,避免重新创建线程和销毁线程。创建线程和销毁线程对CPU的开销是很大的。可以限制最大可创建的线程数,可根据自己的机器性能动态调整线程池参数,提高应用性能。提供定时执行、并发数控制等功能。统一管理线程。

创建线程池的五种方式

1:缓存线程池(不推荐)

2:固定容量线程池(不推荐)

3:单个线程池(不推荐)

4:定时任务线程池(不推荐)

5:通过ThreadPoolExecutor构造方法创建线程池(阿里巴巴开发手册十分推荐)

前面4种创建线程池的方式都是通过Executors的静态方法来创建。

缓存线程池CachedThreadPool

ExecutorService executorService = Executors.newCachedThreadPool();

for (int i = 0; i < 10; i++) {

final int finalI = i;

executorService.execute(new Runnable() {

public void run() {

System.out.println(Thread.currentThread().getName()+"run>"+ finalI);

}

});

}

为什么不推荐使用缓存线程池?

源码分析

public static ExecutorService newCachedThreadPool() {

return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());

}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {

this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);

}

通过上面两个代码片段,我们可以看出CachedThreadPool的maximumPoolSize为Integer的最大值2147483647,相当于可以无限的创建线程,而创建线程是需要内存的,这样就会造成内存溢出,而且一般的机器也没用那么大的内存给它创建这么大量的线程。

固定容量线程池FixedThreadPool

newFixedThreadPool(int num),num就是我们要指定的固定线程数量

ExecutorService executorService = Executors.newFixedThreadPool(5);

for (int i = 0; i < 10; i++) {

final int finalI = i;

executorService.execute(new Runnable() {

public void run() {

System.out.println(Thread.currentThread().getName()+"run>"+ finalI);

}

});

}

输出:

pool-1-thread-5run>4pool-1-thread-4run>3pool-1-thread-5run>5pool-1-thread-3run>2pool-1-thread-3run>8pool-1-thread-3run>9pool-1-thread-2run>1pool-1-thread-1run>0pool-1-thread-5run>7pool-1-thread-4run>6

可以看出起到了线程的复用。

为什么FixedThreadPool是固定线程池?

源码分析

public static ExecutorService newFixedThreadPool(int nThreads) {

return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());

}

通过这个源码可以看出,核心线程数(corePoolSize)和最大线程数(maximumPoolSize)都为nThreads,因为只有这样,线程池才不会进行扩容,线程数才固定。

单个线程池SingleThreadExecutor

ExecutorService executorService = Executors.newSingleThreadExecutor();

for (int i = 0; i < 10; i++) {

final int finalI = i;

executorService.execute(new Runnable() {

public void run() {

System.out.println(Thread.currentThread().getName()+"run>"+ finalI);

}

});

}

为什么SingleThreadExecutor只含有一个线程?

源码分析

public static ExecutorService newSingleThreadExecutor() {

return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));

}

通过这个源码可以看出,核心线程数(corePoolSize)和最大线程数(maximumPoolSize)都为1,所以它只含有一个线程。

定时任务线程池ScheduledThreadPool

int initDelay=10; //初始化延时

int period=1;//初始化延迟过了之后,每秒的延时

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

@Override

public void run() {

System.out.println(Thread.currentThread().getName()+"run>");

}

},initDelay,period, TimeUnit.SECONDS);

这段代码的效果是:程序运行之后等10秒,然后输出第一次结果,之后每隔1秒输出一次结果。

为什么不推荐使用ScheduledThreadPool?

源码分析

public ScheduledThreadPoolExecutor(int corePoolSize) {

super(corePoolSize, 2147483647, 10L, TimeUnit.MILLISECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue());

}

可以看出ScheduledThreadPool的最大线程数(maximumPoolSize)为Integer的最大值2147483647,相当于可以无限的创建线程,而创建线程是需要内存的,这样就会造成内存溢出,而且一般的机器也没用那么大的内存给它创建这么大量的线程。

ThreadPoolExecutor创建线程池(十分推荐)

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,

2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),

Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

for (int i = 0; i < 12; i++) {

final int finalI = i;

threadPoolExecutor.execute(new Runnable() {

public void run() {

System.out.println(Thread.currentThread().getName()+"run>"+ finalI);

}

});

}

Threahttp://dPoolExecutor的七个参数详解

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {

}

corePoolSize:核心线程数。这些线程一旦被创建不会被销毁,是一直存在的。线程池默认是没有线程的,当有任务到来了,就会通过ThreadFactory去创建线程,并一直存在。maximumPoolSize:最大线程数。非核心线程数=maximumPoolSize-corePoolSize,非核心线程数其实就是可扩容的线程数,可能会被销毁。keepAliveTime:非核心线程的空闲存活时间。当通过扩容生成的非核心线程数在keepAliveTime这个时间后还处于空闲状态,则会销毁这些非核心线程。unit:keepAliveTime的时间单位,例如:秒workQueue:等待区。当来了>corePoolSize的任务时会把任务存放在workQueue这个阻塞队列中,等待其他线程处理。threadFactory:线程工厂。创建线程的一种方式。handler:拒绝策略。当来了>最大线程数+workQueue的容量则会执行拒绝策略

workQueue

ArrayBlockingQueue:有界阻塞队列。队列有大小限制,当容量超过时则会触发扩容或者拒绝策略。

public ArrayBlockingQueue(int capacity) {

this(capacity, false);

}

LinkedBlockingQueue:无界阻塞队列,队列无大小限制,可能会造成内存溢出。

public LinkedBlockingQueue() {

this(2147483647);

}

handler

AbortPolicy:直接抛异常

public static class AbortPolicy implements RejectedExecutionHandler {

public AbortPolicy() {

}

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());

}

}

DiscardPolicy:不作任何操作。默默丢弃任务

public static class DiscardPolicy implements RejectedExecutionHandler {

public DiscardPolicy() {

}

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

}

}

DiscardOldestPolicy:丢掉存在时间最长的任务

public static class DiscardOldestPolicy implements RejectedExecutionHandler {

public DiscardOldestPolicy() {

}

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

if (!e.isShutdown()) {

e.getQueue().poll();

e.execute(r);

}

}

}

CallerRunsPolicy:让提交任务的线程去处理任务

public static class CallerRunsPolicy implements RejectedExecutionHandler {

public CallerRunsPolicy() {

}

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

if (!e.isShutdown()) {

r.run();

}

}

}

threadFactory

ThreadFactory threadFactory = Executors.defaultThreadFactory();

threadFactory.newThread(new Runnable() {

@Override

public void run() {

System.out.println("threadFactory");

}

}).start();

如何触发拒绝策略和线程池扩容?

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,

2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),

Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

for (int i = 0; i < 26; i++) { //并发数26

final int finalI = i;

threadPoolExecutor.execute(new Runnable() {

public void run() {

System.out.println(Thread.currentThread().getName()+"run>"+ finalI);

}

});

}

/**

* 核心线程数=10,最大线程数=20,故可扩容线程数=20-10

* BlockingQueue的大小为5,故等待区的大小为5,也就是当并发数<=核心线程数+5不会扩容,并发数大于16才会扩容

*

* 触发扩容:并发数>核心线程数+阻塞队列的大小

* 对于这段代码,如果来了26个并发,10个并发会被核心线程处理,5个会在等待区,剩下11个会因为等待区满了而触发扩容

* 因为这里最多能够扩容10个,这里却是11个,所以会触发拒绝策略

*/

为什么这段代码会触发拒绝策略

对于这段代码,如果来了26个并发,10个并发会被核心线程处理,5个会在等待区,剩下11个会因为等待区满了而触发扩容,但是又因为因为这里最多能够扩容10个,这里却是11个,所以会触发拒绝策略。

怎么触发扩容

触发扩容:并发数>核心线程数(corePoolSize)+阻塞队列(workQueue)的大小

使用java纯手写一个线程池

下期文章链接https://jb51.net/article/241589.htm


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SpringBoot+微信小程序实现文件上传与下载功能详解
下一篇:springboot配置文件中使用${}注入值的两种方式小结
相关文章

 发表评论

暂时没有评论,来抢沙发吧~