深刻理解线程和线程池(图文详解)

关于线程和线程池的学习,咱们能够从如下几个方面入手:缓存

第一,什么是线程,线程和进程的区别是什么安全

第二,线程中的基本概念,线程的生命周期多线程

第三,单线程和多线程并发

第四,线程池的原理解析学习

第五,常见的几种线程池的特色以及各自的应用场景spa


1、线程

线程,程序执行流的最小执行单位,是行程中的实际运做单位,常常容易和进程这个概念混淆。那么,线程和进程究竟有什么区别呢?首先,进程是一个动态的过程,是一个活动的实体。简单来讲,一个应用程序的运行就能够被看作是一个进程,而线程,是运行中的实际的任务执行者。能够说,进程中包含了多个能够同时运行的线程。3d


2、blog

线程的生命周期,线程的生命周期能够利用如下的图解来更好的理解:生命周期


第一步,是用new Thread()的方法新建一个线程,在线程建立完成以后,线程就进入了就绪(Runnable)状态,此时建立出来的线程进入抢占CPU资源的状态,当线程抢到了CPU的执行权以后,线程就进入了运行状态(Running),当该线程的任务执行完成以后或者是很是态的调用的stop()方法以后,线程就进入了死亡状态。而咱们在图解中能够看出,线程还具备一个则色的过程,这是怎么回事呢?当面对如下几种状况的时候,容易形成线程阻塞,第一种,当线程主动调用了sleep()方法时,线程会进入则阻塞状态,除此以外,当线程中主动调用了阻塞时的IO方法时,这个方法有一个返回参数,当参数返回以前,线程也会进入阻塞状态,还有一种状况,当线程进入正在等待某个通知时,会进入阻塞状态。那么,为何会有阻塞状态出现呢?咱们都知道,CPU的资源是十分宝贵的,因此,当线程正在进行某种不肯定时长的任务时,Java就会收回CPU的执行权,从而合理应用CPU的资源。咱们根据图能够看出,线程在阻塞过程结束以后,会从新进入就绪状态,从新抢夺CPU资源。这时候,咱们可能会产生一个疑问,如何跳出阻塞过程呢?又以上几种可能形成线程阻塞的状况来看,都是存在一个时间限制的,当sleep()方法的睡眠时长过去后,线程就自动跳出了阻塞状态,第二种则是在返回了一个参数以后,在获取到了等待的通知时,就自动跳出了线程的阻塞过程

3、

什么是单线程和多线程?

单线程,顾名思义便是只有一条线程在执行任务,这种状况在咱们平常的工做学习中不多遇到,因此咱们只是简单作一下了解

多线程,建立多条线程同时执行任务,这种方式在咱们的平常生活中比较常见。可是,在多线程的使用过程当中,还有许多须要咱们了解的概念。好比,在理解上并行和并发的区别,以及在实际应用的过程当中多线程的安全问题,对此,咱们须要进行详细的了解。

并行和并发:在咱们看来,都是能够同时执行多种任务,那么,到底他们两者有什么区别呢?

并发,从宏观方面来讲,并发就是同时进行多种时间,实际上,这几种时间,并非同时进行的,而是交替进行的,而因为CPU的运算速度很是的快,会形成咱们的一种错觉,就是在同一时间内进行了多种事情

而并发,则是真正意义上的同时进行多种事情。这种只能够在多核CPU的基础下完成。

还有就是多线程的安全问题?为何会形成多线程的安全问题呢?咱们能够想象一下,若是多个线程同时执行一个任务,name意味着他们共享同一种资源,因为线程CPU的资源不必定能够被谁抢占到,这是,第一条线程先抢占到CPU资源,他刚刚进行了第一次操做,而此时第二条线程抢占到了CPU的资源,name,共享资源还来不及发生变化,就同时有两条数据使用了同一条资源,具体请参考多线程买票问题。这个问题咱们应该如何解决那?

  有形成问题的缘由咱们能够看出,这个问题主要的矛盾在于,CPU的使用权抢占和资源的共享发生了冲突,解决时,咱们只须要让一条线程战歌了CPU的资源时,阻止第二条线程同时抢占CPU的执行权,在代码中,咱们只须要在方法中使用同步代码块便可。在这里,同步代码块很少进行赘述,能够自行了解。

四,线程池

又以上介绍咱们能够看出,在一个应用程序中,咱们须要屡次使用线程,也就意味着,咱们须要屡次建立并销毁线程。而建立并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,因此,咱们就提出了线程池的概念。

线程池:Java中开辟出了一种管理线程的概念,这个概念叫作线程池,从概念以及应用场景中,咱们能够看出,线程池的好处,就是能够方便的管理线程,也能够减小内存的消耗。

那么,咱们应该如何建立一个线程池那?Java中已经提供了建立线程池的一个类:Executor

而咱们建立时,通常使用它的子类:ThreadPoolExecutor.

  1. public ThreadPoolExecutor(int corePoolSize,  
  2.                               int maximumPoolSize,  
  3.                               long keepAliveTime,  
  4.                               TimeUnit unit,  
  5.                               BlockingQueue<Runnable> workQueue,  
  6.                               ThreadFactory threadFactory,  
  7.                               RejectedExecutionHandler handler)

这是其中最重要的一个构造方法,这个方法决定了建立出来的线程池的各类属性,下面依靠一张图来更好的理解线程池和这几个参数:


又图中,咱们能够看出,线程池中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收,maximumPoolSize就是线程池中能够容纳的最大线程的数量,而keepAliveTime,就是线程池中除了核心线程以外的其余的最长能够保留的时间,由于在线程池中,除了核心线程即便在无任务的状况下也不能被清除,其他的都是有存活时间的,意思就是非核心线程能够保留的最长的空闲时间,而util,就是计算这个时间的一个单位,workQueue,就是等待队列,任务能够储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。threadFactory,就是建立线程的线程工厂,最后一个handler,是一种拒绝策略,咱们能够在任务满了知乎,拒绝执行某些任务。

线程池的执行流程又是怎样的呢?


有图咱们能够看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,若是不是,核心线程就先就执行任务,若是核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,若是满了,在判断最大可容纳的线程数,若是没有超出这个数量,就开创非核心线程执行任务,若是超出了,就调用handler实现拒绝策略。

handler的拒绝策略:

有四种:第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满

             第二种DisCardPolicy:不执行新任务,也不抛出异常

             第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行

             第四种CallerRunsPolicy:直接调用execute来执行当前任务

五,四种常见的线程池:

CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有须要时建立线程来执行任务,没有须要时回收线程,适用于耗时少,任务量大的状况。

SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。

FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程