java线程池介绍及运行流程

线程模型

线程模型分为:内核线程用户线程

操作系统中内存空间也是分为:内核空间用户空间

    对于操作系统来说,操作系统只能识别内核线程,而对于用户线程,操作系统是无感知的,也就是说不识别的。
 用户线程想得到cpu的使用权,必须要通过内核线程去间接操作。 为什么要这样设计呢?是为了提高安全性,就像是进程隔离一样。

   内核线程才具有cpu的强夺权,用户线程不具备抢夺使用权的资源。



多核cpu可以实现真正的并行吗?

    多核cpu可以实现真正的并行,一个内核线程获取一个核心的cpu的使用率,其他内核线程可以获取其他核心的使用率,从而实现真正并行。

     jvm使用的就是内核线程模型,也就是说当我们手动创建一个线程后,任务管理器中的内核线程数量一会一直加。

    jvm中的线程模型和内核线程是1对1的关系,也就是说在java冲创建一个线程,就会有一个内核线程在操作系统中被创建。

   对于操作系统来说,线程执行并不是说一个线程一直在执行,而是这个线程运行一下,那个线程运行一下,采用一种中断机制;也正是因为这种中断机制,而每次线程中断,都要保护好其他线程运行的线程环境,然后进行线程的上下文切换。
 



为什么要使用线程池?

1、线程的创建和销毁对于操作系统来说是重量级操作。
2、线程频繁的上下文切换,需要大量的资源。
3、可以提高线程的重用性,能够更好的管理线程。
等等



如何使用线程池呢?

jdk提供的api依赖关系图,注意这里只是部分依赖
java线程池介绍及运行流程_第1张图片

jdk中提供了五种线程池。

在jdk中提供了一个工具类Executors可以来创建线程池。
以下是Executors类的结构。
java线程池介绍及运行流程_第2张图片

newFixedThreadPool:固定线程数的线程池。

newWorkStealingPool:可以偷其他线程队列中的任务,线程池中维护了多个任务队列。

newSingleThreadExecutor:一个线程的线程池。

newCachedThreadPool:无界限创建线程的线程池。

newScheduledThreadPool:可以定时的线程线程池。

一般我们会选择哪个线程池呢?

答案是:哪个都不选。

为什么哪个都不选呢?让我们看下一下这些提供出来的线程池是如何创建的。
例如:newFixedThreadPool线程池,它的内部创建了一个LinikedBlockingQueue的队列。
这个队列就是用来存储任务的队列。
java线程池介绍及运行流程_第3张图片
继续点进去,发现它是创建了一个Integer.MAX_VALUE大小的队列,这里的大小就是2^31-1
java线程池介绍及运行流程_第4张图片
试想一下,如果有大量的请求一直发送到后台,这个队列就可以无限的接受队列。就很容易出现OOM异常。

可以查看一下其他几种线程池,发现除了newWorkStealingPool,其他四个都是这样子的。

那我们到底如何使用线程呢?

从上图我们可以看到,其实Executors提供创建的线程池的方法都是在创建ThreadPoolExecutor对象。我们就是要来使用ThreadPoolExecutor自己创建线程池。

首先,让我们来看下ThreadPoolExecutor有哪些参数:

1、int corePoolSize:
 核心工作线程数,线程池启动创建的线程数。

2、int maximumPoolSize:
 最大线程数,平时只有核心数个线程在执行,如果任务特别多,任务队列满了之后,就会出创建临时线程。
 能够创建多少个临时线程呢?临时线程数=最大线程数-核心工作线程数

3、long keepAliveTime:
 临时线程空闲最大的存活时间。
 
4、TimeUnit unit:
 存活时间单位。

5、BlockingQueue workQueue:
 当任务数超过核心工作线程数之后用于存储任务的队列。。
 任务队列也有分:
  有界队列
    只能存储指定数量个任务进队列中。
    
  无界队列
   可以无限将任务存储进队列中,只要硬件条件允许。
  
  阻塞队列
   同一个时刻只能一个一个任务入队列,是有序的,是线程安全的。

6、ThreadFactory threadFactory:
 线程创建工厂,一般我们使用默认的Executors.defaultThreadFactory()

7、RejectedExecutionHandler handler:
 拒绝策略,当任务数超过最大线程数之后,并且任务队列也存储满了,如果还有任务到来,任务就无法处理。拒绝策略就是为了解决这种问题而产生的。
 
 默认提供了四种拒绝策略。默认使用的是AbortPolicy,也可以自己自定义拒绝策略。例如:如果任务队列满了,可以记录到日志中,然后在进行重新入队操作。
 
jdk提供的四种策略:
AbortPolicy:
 丢弃任务并抛出RejectedExecutionException异常。
 
DiscardPolicy:
 如果任务队列满了,直接丢弃任务,也不抛出异常。
 
CallerRunsPolicy:
 由提交任务到线程池的那个线程来处理该任务。
 
 
DiscardOldestPolicy:
 丢弃任务队列中最前面的任务,并且将当前提交的任务存入任务队列中。


创建拒绝策略对象:
例如:

ThreadPoolExecutor.DiscardPolicy discardPolicy = new ThreadPoolExecutor.DiscardPolicy();

也可以自定义拒绝策略,创建一个类实现RejectedExecutionHandler接口。




线程池运行流程

    线程池启动,初始化线程池。线程池通过线程工厂创建核心线程。
 当任务过来之后,先将任务交给核心线程执行;如果核心线程还有,则当任务来了之后,直接创建核心线程并且将任务交给核心线程处理。
 如果没有足够的核心线程,就将任务存入任务队列中,如果任务队列满了,则创建临时线程,然后将任务交给临时线程处理。如果临时线程数+核心线程数创建的数量等于最大线程数;并且任务队列也满了,则会执行拒绝策略。




线程池什么时候才会销毁呢?

1、corePoolSize设置为0,只设置最大线程数,这样所有的线程都是临时线程,只要任务处理完了之后,并且线程超过了keepAliveTime时间,就会被销毁了。
2、jdk1.6起,支持allowCoreThreadTimeOut,表示核心线程也和临时线程一样接受keepAliveTime的管理。
所以,只要线程池调用allowCoreThreadTimeOut(true) 方法,设置allowCoreThreadTimeOut的值为true。
3、添加完任务之后,使用shutdown()方方法,当所有的任务执行完之后,就会自动关闭线程池了,需要注意的是,调用了shutdown()方法后,线程池不能接收新的任务了。

注意:
 1、任务队列满了才会创建非核心线程数去执行任务,并且如果非核心线程空闲了一段时间 (保活时间keepAliveTime) 之后,这些线程会被销毁。

你可能感兴趣的:(java)