关于线程池
1. 为什么不能用Executors创建线程池?
1.1 CachedThreadPool可缓存的线程池
两种实现方式 :
Executors.newCachedThreadPool();
Executors.newCachedThreadPool(new TaskThreadFactory("" , false , 2));
默认实现 : new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
因为最大连接数是Integer.MAX_VALUE . 当访问请求过大时 , 易造成OOM
1.2 FixedThreadPool(固定大小的线程池)
Executors.newFixedThreadPool(1);
Executors.newFixedThreadPool(1 , new TaskThreadFactory("" , false , 2));
默认实现方式 :
new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
因为LinkedBlockingQueue队列的最大值为Integer.MAX_VALUE , 可以认为是无界队列 , 也就可以往队列中插入无限多的任务 , 也会造成OOM
1.3 ScheduledThreadPool(可定时执行线程池)
public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); } public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory); }
因为只支持了两种实现方式 , 且参数只有 : 核心线程数和工厂两种 , 其最大连接数默认为Integer.MAX_VALUE , 一样会造成内存过大
1.4 SingleThreadExecutor(单线程线程池)
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
同FixedThreadPool一样 , 因为其用的LinkedBlockingQueue为无界队列 , 其队列最大容量默认为Integer.MAX_VALUE , 资源不允许的情况下 , 易造成OOM .
1.5 无界队列和有界队列
常见的有界队列 :
- ArrayBlockingQueue: 基于数组实现的阻塞队列
- LinkedBlockingQueue 当设置其容量时也是有界队列 , 但是在Executors中默认为Integer.MAX_VALUE , 此时可以理解为是无界队列 . 基于链表实现的
- ArrayBlockingQueue 实现简单,表现稳定,添加和删除使用同一个锁,通常性能不如后者
- LinkedBlockingQueue 添加和删除两把锁是分开的,所以竞争会小一些
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
共同点 :
- put take 操作都是阻塞的
- offer poll 操作不是阻塞的,offer 队列满了会返回false不会阻塞,poll 队列为空时会返回null不会阻塞
- 补充一点,并不是在所有场景下,非阻塞都是好的,阻塞代表着不占用CPU,在有些场景也是需要阻塞的,put take 存在必有其存在的必然性
无界队列
- PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列。
- DealyQueue:一个使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列,也可理解为无界阻塞队列 .
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
共同点 :
- put 操作永远都不会阻塞,空间限制来源于系统资源的限制
- 底层都使用CAS无锁编程
1.6 公平锁和非公平锁
公平锁 : 多个线程按照申请锁的顺序去获得锁 , 线程会进入队列 , 遵循FIFO的原则去获取锁 .
优点 : 可保证线程有序进行 , 且所有线程都会获得目标资源 , 不会饿死在 .
缺点 : 对性能损耗较大 , 吞吐量会下降 . 除了第一个线程,其他的线程都会阻塞,
非公平锁 : 多个线程共同去获取锁的时候 , 会先去争取锁 , 获取不到的情况下 , 才进入队列等待 .
优点 : 减少CPU唤醒线程的开销 , 吞吐量会高一些 , CPU不必去唤醒所有的线程 , 减少唤醒线程的数量 .
缺点 : 无序 . 且可能导致某些线程一直获取不到锁 . 导致线程失效的情况 .
常见类 : FairSync和NofairSync
1.7 线程的生命周期
- 新建(NEW):新创建了一个线程对象。
- 可运行(RUNNABLE): 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
- 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
- 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
- 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可