线程池
相关参考的blog:
https://blog.csdn.net/xu_yong_lin/article/details/117521773
https://zhuanlan.zhihu.com/p/60986630
线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Executors.newCachedThreadPool 源码解析
Executors 还有个常用静态方法newCachedThreadPool(),来构造线程池
今天我们其源码实现,探一探究竟
//底层还是调用ThreadPoolExecutor,不过参数有变化
//corePoolSize 竟然为0,maximumPoolSize为默认的最大值
//当任务队列满时,就会判断maximumPoolSize大小
//keepAliveTime 空闲线程的最大等待时间,,60s后立马销毁线程了
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
####SynchronousQueue
注意这个队列
A {@linkplain BlockingQueue blocking queue} in which each insert
* operation must wait for a corresponding remove operation by another
* thread, and vice versa. A synchronous queue does not have any
* internal capacity, not even a capacity of one.
- SynchronousQueue,实际上它不是一个真正的队列,因为它不会为队列中元素维护存储空间。与其他队列不同的是,它维护一组线程,这些线程在等待着把元素加入或移出队列。
- 在使用SynchronousQueue作为工作队列的前提下,客户端代码向线程池提交任务时,而线程池中又没有空闲的线程能够从SynchronousQueue队列实例中取一个任务,那么相应的offer方法调用就会失败(即任务没有被存入工作队列)。此时,ThreadPoolExecutor会新建一个新的工作者线程用于对这个入队列失败的任务进行处理(假设此时线程池的大小还未达到其最大线程池大小maximumPoolSize)。
newFixedThreadPool 和 newCachedThreadPool最大差别就是 队列,线程回收的时间
newFixedThreadPool 应用场景
Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available. These pools will typically improve the performance
* of programs that execute many short-lived asynchronous tasks.
* Calls to <tt>execute</tt> will reuse previously constructed
* threads if available. If no existing thread is available, a new
* thread will be created and added to the pool. Threads that have
* not been used for sixty seconds are terminated and removed from
* the cache. Thus, a pool that remains idle for long enough will
* not consume any resources.
newFixedThreadPool 线程池的数量是不确定的,可以无限大。
它比较适合处理执行时间比较小的任务
Executors 工厂的实现
//创建CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//创建FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
以上便是工厂 Executors 中创建线程池的具体实现。从实现代码中,可以看出,不同特性的线程池本质都是构建 ThreadPoolExecutor 对象。查看 ThreadPoolExecutor 类的源码可以看到,其构造方法定义如下。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> 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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
构造参数简介如下: - corePoolSize:线程池中一直有的线程个数,默认情况下即使空闲也不会被回收(可以通过设置allowCoreThreadTimeOut参数来改变默认) - maximumPoolSize:线程池中可以持有的最多线程数 - keepAliveTime:超过corePoolSize数的空闲线程在被销毁之前等待新任务到达的最长时间 - unit:keepAliveTime参数的单位 - workQueue:线程池的等待队列,被execute方法提交的任务将进入这一队列,默认无限大 - threadFactory:线程工厂,可以自定义线程的创建过程 - handler:拒绝处理器,负责在workQueue满的时候处理新提交的任务
反观Executors工厂的实现,可以看出,针对于 FixedThreadPool 的创建,其实就是创建一个核心线程和最大线程均为固定值的线程池,以保证只有固定个线程提供服务;针对于 CachedThreadPool 的创建,则是创建一个核心线程数为0、最大线程数为整型最大值的线程池。
FixedThreadPool 存在的问题
由代码可以看到,除却核心线程和最大线程数都设置为固定值,FixedThreadPool还使用了一个无长度限制的等待队列。 在使用上,通常都会认为FixedThreadPool是不会占过多资源的。但是使用中,FixedThreadPool仍然会可能出现 OOM 的风险。这是因为,由于FixedThreadPool采用无界的等待队列,一旦空闲线程被用尽,就会向队列中加入任务,这时一旦任务进入速度远高于线程处理能力,就有出现 OOM 的可能。 阿里巴巴编码规范中,也有关于线程池的使用说明。其建议通过直接定义 ThreadPoolExecutor 来代替使用 Executors 提供的工厂方法。在我们处理的数据量较大或者并发量很大时,应避免直接使用 Executors 提供的 FixedThreadPool。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
LinkedBlockingQueue的长度为Integer.MAX_VALUE,虽然使用 newFixedThreadPool 可以把工作线程控制在固定的数量上,但任务队列是无界的。如果任务较多并且执行较慢的话,队列可能会快速积压,会导致内存 OOM。
SynchronousQueue
SynchronousQueue是无界的,是一种无缓冲的等待队列,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加;可以认为SynchronousQueue是一个缓存值为1的阻塞队列,但是 isEmpty()方法永远返回是true,remainingCapacity() 方法永远返回是0,remove()和removeAll() 方法永远返回是false,iterator()方法永远返回空,peek()方法永远返回null。
声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为。公平模式和非公平模式的区别:如果采用公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;但如果是非公平模式(SynchronousQueue默认):SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。
LinkedBlockingQueue
LinkedBlockingQueue是无界的,是一个无界缓存的等待队列。
基于链表的阻塞队列,内部维持着一个数据缓冲队列(该队列由链表构成)。当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。
LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
ArrayListBlockingQueue
ArrayListBlockingQueue是有界的,是一个有界缓存的等待队列。
基于数组的阻塞队列,同LinkedBlockingQueue类似,内部维持着一个定长数据缓冲队列(该队列由数组构成)。ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。
ArrayBlockingQueue和LinkedBlockingQueue是两个最普通、最常用的阻塞队列,一般情况下,处理多线程间的生产者消费者问题,使用这两个类足以。
使用 一个
https://blog.csdn.net/qq_34244429/article/details/85029014
package com.example.myaop.provider;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 8, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
LinkedBlockingQueue queue = new LinkedBlockingQueue(3);
MyConsumer myConsumer = new MyConsumer(queue);
MyProvider myProvider = new MyProvider(queue);
MyConsumer myConsumer2 = new MyConsumer(queue);
MyProvider myProvider2 = new MyProvider(queue);
executor.execute(myProvider);
executor.execute(myProvider2);
executor.execute(myConsumer);
executor.execute(myConsumer2);
Thread.sleep(3000);
myProvider.toStop();
myProvider2.toStop();
while(true){
Thread.sleep(1000);
int activeCount = executor.getActiveCount();
System.out.println("alive thread count:"+activeCount);
if(activeCount == 0){
System.out.println("executor shutdown");
executor.shutdown();
break;
}
}
}
}
package com.example.myaop.provider;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
*
*生产者
*/
public class MyProvider implements Runnable {
private volatile LinkedBlockingQueue queue;
private static boolean flag = false;
public MyProvider(LinkedBlockingQueue queue) {
this.queue = queue;
}
public void toStop(){
System.out.println("stop..");
this.flag=true;
}
@Override
public void run() {
while (!flag){
try {
queue.put(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))+"mytime");
queue.put("mytime");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(Thread.currentThread().getName()+" sleep 500ms");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.example.myaop.provider;
import org.springframework.util.StringUtils;
import java.util.concurrent.LinkedBlockingQueue;
public class MyConsumer implements Runnable{
private volatile LinkedBlockingQueue queue;
private static boolean stopFlag=false;
public MyConsumer(LinkedBlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
System.out.println("consumer:"+Thread.currentThread().getName()+" running");
int count = 0;
while(!stopFlag){
try {
Object take = queue.poll();
if(take != null){
count =0;
System.out.println("consumer:"+take);
Thread.sleep(1000);
}else{
count++;
Thread.sleep(500);
System.out.println("consumer waiting");
}
if(count >= 20){
System.out.println(Thread.currentThread().getName()+"consumer stop");
stopFlag=true;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
ThreadFactory 示例
package com.tencent.luoshu.thrift;
import java.util.concurrent.ThreadFactory;
public class NamedThreadFactory implements ThreadFactory {
private final String name;
private final Boolean daemon;
NamedThreadFactory(String name, Boolean daemon) {
this.name = name;
this.daemon = daemon;
}
public Thread newThread (Runnable r) {
Thread t = new Thread(r);
t.setName(name + ": Thread-" + t.getId());
t.setDaemon(daemon);
return t;
}
}