线程池底层原理介绍
池化思想
优点
- 提高线程利用率
- 提高响应速度
- 可以控制最大并发数
- 便于统一管理
参数
参数 | 解释 |
---|---|
corePoolSize | 核心线程数量,线程池维护线程的最少数量 |
maximumPoolSize | 线程池维护线程的最大数量 |
keepAliveTime | 线程池除核心线程外的其他线程的最长空闲时间,超过该时间的空闲线程会被销毁 |
unit | keepAliveTime的单位,TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS |
workQueue | 线程池所使用的任务缓冲队列 |
threadFactory | 线程工厂,用于创建线程,一般用默认的即可 |
handler | 线程池对拒绝任务的处理策略 |
处理过程
1.创建一个线程池,线程池里有多个核心线程,当任务进来时,当前线程数量小于corePoolSize时,会立刻创建线程不管有米有空闲线程;
2.当任务进来后,如果有空闲的线程,则会立刻调用线程,如果没有空闲的线程就会将任务放到一个阻塞队列中,直到有空闲的线程出现;
3.当阻塞队列满时,线程池才会尝试创建新的线程,如果线程数量大于最大线程数,则会采取拒绝任务的策略
4.当后来创建的线程空闲达到一定时间后,会自动销毁,直到满足核心线程数。
WorkQueue
主要有四类:
- ArrayBlockingQueue:有界阻塞队列,一般使用的这个,当任务量大于队列长度时,要么创建线程要么拒绝任务
- LinkedBlockingQueue:无界阻塞队列,maxmumPoolSize无效,会一直加入线程,不会创建新的线程去处理任务
- SynchronousQueue:同步队列,不能存放任务,一旦有任务进来,必须创建线程来处理任务
- PriorityBlockingQueue:优先阻塞队列,具有优先级的无界阻塞队列
Handler
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;也是默认的处理方式。
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
常见的线程池
- Executors.newCachedThreadPool
new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue());
使用SynchronousQueue,有任务进来就创建线程,线程空闲就回收销毁 - Executors.newFixedThreadPool
new ThreadPoolExector(n Threads,n Threads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())
线程数固定,不能创建新的线程,如果有任务进来就阻塞 - Executors.newSingleThreadExecutor();
new ThreadPoolExector(n Threads,n Threads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照顺序执行。 - Executors.newScheduledThreadPool(int);
new ScheduledThreadPoolExecutor(corePoolSize)
创建一个定长线程池,支持定时及周期性任务执行。
核心线程数选择
cpu密集型任务:系统的I/O读写效率高于CPU效率,大部分的情况是CPU有许多运算需要处理,使用率很高。但I/O执行很快。那么核心线程数为cpu+1
IO密集型任务:系统的CPU性能比磁盘读写效能要高很多,大多数情况是CPU在等I/O的读写操作,此时CPU的使用率并不高;核心线程数为cpu*2
混合型任务:既包含CPU密集型又包含I/O密集型。核心线程数为(线程等待时间/线程cpu时间+1)*cpu
核心线程数过多或者过少的问题
核心线程数过多会导致线程之间竞争激烈,上下文切换频繁,从而影响整体效率
核心线程数过少会导致大量任务进来后,会导致任务处于阻塞状态需要长时间等待,或者队列满了之后有任务无法被执行,甚至导致内存溢出(OOM)。
创建线程池的方式
- 使用Executors工具类
- 使用ThreadPoolExector类去创建
execute()方法和submit()方法的区别
execute()方法提交的是没有返回值的任务,无法判断该任务时候被线程池执行
submit()方法提交的是有返回值的任务,返回类型是future类,通过future类的get()方法获取返回值时会阻塞当前线程直到得到结果,get(long timeout,TimeUnit unit)方法是阻塞一段时间后返回,任务不一定完成,也有可能完成。cancel方法可以取消任务,已经完成或者还未开始任务则返回false,任务正在执行则返回true并且中断任务。
线程池调优
核心线程数选择
最大线程数设置,防止县城资源耗尽
使用有界的阻塞队列,提高系统的稳定性和预警能力