目录:
线程的创建方式
线程池的优势
Runable和Callable接口的区别
java的几种线程池(面试时被问过)
线程池的七大参数(面试时被问过)
线程池底层工作原理
线程池的拒绝策略
首先线程池
如何确定这些参数(面试时被问过)
死锁编码及定位分析
1、线程的创建方式
(1)继承Thread类;很少用,实际都是面向接口编程。
(2)实现Runable接口 ;
(3)实现Callable接口(JDK>=1.5);
(4)线程池创建。
2、线程池的优势
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务。如果线程数量超出了核心线程数,超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。如果线程排队的数量已经挤满了工作队列,则此时会根据核心线程数的值,添加临时线程去处理工作队列的任务。
他的主要特点为:线程复用;控制最大并发数;管理线程。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
3、Runable和Callable接口的区别
(1)Runable没有返回值,Callable接口有返回值;
(2)接口实现的方法不一样,Runable实现run方法,Callable实现call方法;
(3)Callable可以声明抛出异常。
发现没有一个Thread实现了Callable接口,都是Runnable接口,于是要通过一个中间件适配器即实现了Runnable接口又实现了Callable接口,即futureTask方法
线程池是通过Executor框架实现的,该框架中Executor,Executors(工具类),ExecutorService
其中三个工具类:Array Arrays(Arrays.aslist());Collection Collections; Executor Executors。
线程池的底层就是ThreadPoolExecutor类
4、java的几种线程池
1.Executors.newFixedThreadPool(int); 执行长期的任务
2.Executors.newSingleThreadExecutor; 一个任务执行
3.Executors.newCachedThreadpool(); 执行短期异步或负载较轻任务
创建:ExecutorService threadPool=Executors.newFixedThreadPool(int) 但是这三种底层都是通过ThreadPoolExecutor类实现的。但阿里巴巴规范里,这三个都不用而是手写线程池。
5、线程池的七大参数
5.1、七大参数:
- corePoolSize——线程池中常驻核心线程数,当值窗口数,超过这个数就会放到缓存队列
- maximumPoolSize——线程池能容纳最大的线程数
- keepAliveTime——多余的空闲线程的存活时间,超过corePoolSize的线程数叫空闲线程
- Unit——keepAliveTime的单位
- workQueue——任务队列,被提交但还未执行的任务。
- handler——拒绝策略,线程数量达到maximumPoolSize时的策略,默认提供了4种:
- threadFactory——创建线程时使用的工厂,可以对线程进行统一设置,如是否守护线程、线程名等
5.2、几种主要的阻塞队列
ArrayBlockingQueue:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。
LinkedBlockingQueue:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。
PriorityBlockingQueue:以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志),前面2种都是有界队列。
DelayQueue:基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。6、线程池底层工作原理
1.创建线程池,等待提交过来的任务请求。
2.添加任务请求时,分为以下几种情况:
2.1如果正在运行的线程数量小于corePoolSize,就创建线程处理
2.2 如果大于corePoolSize,就将任务放入阻塞任务队列
2.3 如果队列满了但线程数量小于maximumPoolSize,就创建非核心线程去处理任务
2.4 如果队列满了且正在运行的数量大于等于maximumPoolSize,就启动饱和拒绝策略
3.当一个线程完成任务,就会从队列取下一个
4.当一个线程一定时间内没有任务处理,就会判断正在运行数的线程树是否大于corePoolSize,如果是,则大于该线程数的多余线程就被停掉;所以最后会减小到corePoolSize的数量大小。7、线程池的拒绝策略
AbortPolicy(默认): 直接丢弃并抛出异常RejectedExecutorException。
CallerRunsPolicy: “调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用处。
DiscardPolicy: 直接丢弃任务,不予任何处理,也不抛出异常,如果允许任务丢失,这是最好的一种方案。
DiscardOldestPolicy: 丢弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。8、如何确定这些参数
8.1、手写线程池
public void main(String[] args) {
ExceptorService threadPool = new ThreadPoolExceptor(
2, // corePoolSize
5, // MaximunPoolSize
1L, // keepAliveTime
TimeUnit.SECONDS,
new LinkedBlockingQuene<>(3)
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
8.2、如何确定这些参数
1、CPU密集型任务配置尽可能少的线程数量,尽量减少切换:CPU核数+1个线程的线程池
2、IO密集型:
(1)由于IO并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2
(2)CPU核数/(1-阻塞系数)阻塞系数在0.8到0.9间9、死锁编码及定位分析