目录:
线程的创建方式
线程池的优势
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可以声明抛出异常。
image.png
发现没有一个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、线程池底层工作原理

    image.png
    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、手写线程池

    1. public void main(String[] args) {
    2. ExceptorService threadPool = new ThreadPoolExceptor(
    3. 2, // corePoolSize
    4. 5, // MaximunPoolSize
    5. 1L, // keepAliveTime
    6. TimeUnit.SECONDS,
    7. new LinkedBlockingQuene<>(3)
    8. Executors.defaultThreadFactory(),
    9. new ThreadPoolExecutor.AbortPolicy()
    10. );
    11. }

    8.2、如何确定这些参数

    1、CPU密集型任务配置尽可能少的线程数量,尽量减少切换:CPU核数+1个线程的线程池
    2、IO密集型:
    (1)由于IO并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2
    (2)CPU核数/(1-阻塞系数)阻塞系数在0.8到0.9间

    9、死锁编码及定位分析

    image.png