线程池好处
- 降低资源消耗
通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 - 提高响应速度
当任务到达时,任务可以不需要等到线程创建就能立即执行。 - 提高线程的可管理性
线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用
线程池创建方式
newCachedThreadPool
- 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
- 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程
- 线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小
ExecutorService newCacheThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int temp = i; newCacheThreadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "," + temp); }); }
newFixedThreadPool
- 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
- 定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int temp = i; newFixedThreadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "," + temp); }); }
newScheduledThreadPool
- 创建一个定长线程池,支持定时及周期性任务执行
- ScheduledExecutorService比Timer更安全,功能更强大
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3); for (int i = 0; i < 10; i++) { final int temp = i; //隔3秒后执行线程 newScheduledThreadPool.schedule(() -> System.out.println(Thread.currentThread().getName() + "," + temp), 3, TimeUnit.SECONDS); }
newSingleThreadExecutor
- 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
- 现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int temp = i; newSingleThreadExecutor.execute(() -> { System.out.println(Thread.currentThread().getName() + "," + temp); }); }
线程池原理
1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务
自定义线程池
java.util.concurrent.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
线程池维护线程的最少数量 ,实际运行线程 - maximumPoolSize
线程池维护线程的最大数量 ,最多可以创建多少个线程 - keepAliveTime
线程池维护线程所允许的空闲时间 - unit
线程池维护线程所允许的空闲时间的单位 - workQueue
线程池所使用的缓冲队列
ArrayBlockingQueue:数组结构组成的有界阻塞队列
LinkedBlockingQueue:链表结构组成的有界阻塞队列
PriorityBlockingQueue:支持优先级排序的无界阻塞队列
DealyQueue:使用优先级队列实现的无界阻塞队列
SynchronousQueue:不存储元素的阻塞队列
LinkedTransferQueue:链表结构组成的无界阻塞队列
LinkedBlockingDeque:链表结构组成的双向阻塞队列 - handler
线程池对拒绝任务的处理策略
ThreadPoolExecutor.AbortPolicy():抛出java.util.concurrent.RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy() :重试添加当前的任务,他会自动重复调用execute()方法
ThreadPoolExecutor.DiscardOldestPolicy() :抛弃旧的任务
ThreadPoolExecutor.DiscardPolicy() :抛弃当前的任务
自定义线程池样例
-
代码
public static void main(String[] args) { ThreadPoolExecutor poolExecutor =new ThreadPoolExecutor(1,2,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(3)); poolExecutor.execute(()-> System.out.println(Thread.currentThread().getName())); poolExecutor.execute(()-> System.out.println(Thread.currentThread().getName())); poolExecutor.execute(()-> System.out.println(Thread.currentThread().getName())); poolExecutor.execute(()-> System.out.println(Thread.currentThread().getName())); }
-
执行结果
pool-1-thread-1 pool-1-thread-1 pool-1-thread-1 pool-1-thread-1
执行结果可以看出,我们新建的线程池corePoolSize=1,maximumPoolSize=2,workQueueSzie=3,当我们执行四个任务时,线程池只开启了一个线程执行任务,由线程池原理图流程可以看出,我们后续的任务都会存放到了workQueueSzie中,由于缓存队列并没有满,则没有开启新的线程数执行任务。
-
新增一个任务执行结果
pool-1-thread-1 pool-1-thread-1 pool-1-thread-1 pool-1-thread-1 pool-1-thread-2
执行结果可以看到出,当我们执行五个任务的时候,开启了第二个线程执行任务,是由于当第五个任务进入时,此时核心线程数=1,缓存队列=3(已满),当第五个任务进入时,则会判断是否大于最大线程数,此时最大线程数=2,执行线程数=1,则创建了一个新的线程执行任务
-
再次增加一个任务执行结果
pool-1-thread-1 pool-1-thread-1 pool-1-thread-1 pool-1-thread-1 pool-1-thread-2 Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task DemoThread17$$Lambda$8/1480010240@4dd8dc3 rejected from java.util.concurrent.ThreadPoolExecutor@6d03e736[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 3] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) at DemoThread17.main(DemoThread17.java:20)
当执行第六个任务时,则抛出了异常,是由于当前核心线程数=2,缓存队列=3,第六个任务进入时,执行了拒绝任务策略
合理配置线程池
CPU密集
- CPU密集是该任务需要大量的运算,而没有阻塞,CPU一直全速运行
- CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那些
- CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务
IO密集
- IO密集型,即该任务需要大量的IO,即大量的阻塞
- 在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待
- 在IO密集型任务中使用多线程可以大大的加速程序运行,即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间
- IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数