• 线程与线程池


    一、线程的生命周期有6种状态:

    线程的生命周期有6种,分别是新建状态、运行状态、阻塞状态、等待状态、超时等待状态和终止状态

    线程在自身的生命周期中,并不是固定的处于某个状态,而是随着代码的执行在不同的状态之间进行切换:
    当线程通过new方法被创建之后,调用start()方法开始运行。
    当线程执行当程序执行wait()方法后,线程进入等待状态(java将操作系统中的运行状态和就绪状态合并为运行状态),
    进入等待状态的线程需要依靠其它线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超过时间之后将会返回到运行状态。
    当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入阻塞状态。
    线程在执行Runnable的run方法之后就会进入到终止状态。

    新建(new):线程被创建,但是还没有调用start方法
    运行状态(runnable):线程创建后,调用start()方法开始运行。java线程将操作系统的就绪和运行两种状态笼统的称 作为“运行中”
    阻塞状态(blocked):当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入阻塞状态。
    等待状态waiting):当程序执行wait()方法后,线程进入等待状态,进入等待状态的线程需要依靠其它线程的通知才能够 返回到运行状态
    超时等待状态(time_waiting):该状态不同于waitting,它在等待状态的基础上增加了超时限制,也就是超过时间之后 将会返回到运行状态
    终止状态(terminated):表示当前线程已经被执行完毕

    操作系统层面上的生命周期:

    新建、就绪、运行、阻塞、死亡

    二、实现线程的四种方法

    1)继承Thread类,重写run( )方法,然后直接new对象,使用start( )方法执行
    2)实现runnable接口,实现run( )方法,创建出runnable接口的实现类对象,传入Thread的构造方法中,Thread对象才是真正的线程对象,调用线程的start( )方法来启动线程
    3)实现callable接口,重写call( )方法,然后创建出callable接口的实现类对象,传入FutureTask类的构造方法中,最后将futuretask对象传入Thread的构造方法中,调用线程的start( )方法来启动线程,
    call方法比run方法功能要强大,call方法可以有返回值,可以声明抛出异常 4)线程池:通过线程池获取线程 问题一:线程的run()和start()有什么区别? 1)调用 start()方法是用来启动线程的,轮到该线程执行时,会自动调用 run();直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。 ​2)一个线程的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常run() 方法没有限制。 问题二:为什么callable接口实现类对象不直接传入Thread的构造方法中? 上面我们已经看了Callable接口的源码,其中只有一个call方法的定义,下面我们看一下FutureTask<V>类的定义,其中我们可以看到FutureTask<V>类是实现了RunnableFuture<V>接口的
    .我们再看FutureTask<V>类其中的一个构造方法如下,其中需要传入一个Callable<V>类型的参数
    更多内容可以自行查看FutureTask<V>类的具体实现,这里我们只需要知道此构造方法传入一个Callable<V>类型的参数,然后赋值给FutureTask<V>中的私有属性

    三、常见的线程池类型(3种,使用Excurors创建)

    Executors.newFixedThreadPool( int ) 一固定线程池(底层任务队列采用LinkedblockingQueue)

    Executors.newSingleThreadExecutor()一单线程池 (底层请求队列采用LinkedblockingQueue)

    Executors.newCachedThreadPool( ) 一多线程池 (底层请求队列采用SynchronousQueue)

    其底层实现类都是ThreadPoolExecutor,只是构造时的参数不同

    在生产中以上3种jdk提供的线程池都不用,通常都是自己定义线程池并使用

    四、线程池七大参数

     public ThreadPoolExecutor(int corePoolSize,                     核心线程数
    ​                           int maximumPoolSize,                  最大线程数
    ​                            long keepAliveTime,                  空闲线程的存活时间
    ​                            TimeUnit unit,                       存活时间的单位
    ​                            BlockingQueue<Runnable> workQueue,   任务队列
    ​                            ThreadFactory threadFactory,         线程工厂
    ​                            RejectedExecutionHandler handler)    拒绝策略

    1)corePoolSize:线程池中的常驻核心线程数
    ​ 在创建了线程池后,当有请求任务来后,就会安排池中的线程区执行请求任务,​ 当线程池中的线程数达到corePoolSize后,就会把任务放到缓存队列中。
    线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
    ​ 2)maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1。​ 在核心线程数满了,并且任务队列中的等待任务也达到最大,会扩充核心线程数。
    一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,
    才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。

    ​ 3)keepAliveTime:多余的空闲线程的存活时间
    ​ 一个线程如果处于空闲状态,且当前的线程数量超过corePoolSize时,那么在指定的时间后,这个空线程会被销毁,这里的指定时间由keepAliveTime来设定,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止
    ​ 4)unit:keepAliveTime的单位
    ​ 5)workQueue:任务队列,被提交但尚未被执行的任务
    ​ 6)threadFactory:表示生产线程池中工作线程的线程工厂,用于创建线程,一般用默认即可
    ​ 7)handler: 拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数, 这时如果有新任务提交进来,该如何处理呢。 这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:

    五、线程池的底层原理

    线程池的底层: ThreadPoolExecutor
    1.在创建了线程池后,等待提交过来的任务请求
    ​2.当调用execute( )方法添加一个请求任务时,线程池会做如下判断;
      1)如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务
      2)如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
      3)如果这时候队列满了并且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心 线程立刻运行这个任务;
    ​  4)如果队列满了,并且正在运行的线程数量已经到达了maxmumPoolSize,那么线程池会启动饱和拒绝策略来执行
    3.当一个线程完成任务时,它会从队列中取下一个任务来执行
    ​4.当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:如果当前运行的线程数大于corePoolSize,那么这个线程就会被终止
    所以线程池的所有任务完成后最终会收缩到corePoolSize的大小

    六、线程池的4中拒绝策略 

    1.AbortPolicy(默认):直接抛出RejectedExecutionException异常 阻止系统正常运行
    2.CallerRunsPolicy:调用当前线程池所在的线程去执行被拒绝的任务。
    3.DiscardOldestPolicy:抛弃进入队列最早的一个任务, 然后尝试把这次拒绝的任务放入队列
    ​4.DiscardPolicy: 直接丢弃任务,不给予任何处理也不抛出异常,如果允许任务丢失,这是最好的一种方案
      CPU密集型任务应该尽可能配置小的线程数
      ​O密集型应尽可能配置多的线程数
    ​      可以使用executesubmit两个方法向线程池提交任务,
          execute方法用于不需要返回值的任务,所以无法判断任务是否被线程执行成功
          submit用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过这个future对象判断任务是否执行成功,并且可以通过future的get方法来获取返回值,get( )方法会阻塞当前线程直到任务完成

    七、如何安全结束一个线程

    方法一:通过中断状态
    ​中断状态是线程的一个标识位,中断操作是一种简便的线程间的交互方式,适合用来取消或停止任务
    例子:
    在 A 线程中 调用 B.interrupt( ) // A 线程对B线程进行了中断,修改了它的中断标志位
    ​B线程在重写run方法的时候,并在while循环中判断Thread.currentThread.isInterrupted( )如果被 中断,就跳出while结束,即结束线程了
    方法二:利用一个boolean变量来控制是否需要停止任务
    在B线程中使用一个boolean on 标记,并在while循环中判断on是不是等于false
    例子:在A线程中 将B.on 修改为 false // A 线程对B线程进行了中断
    B线程在重写run方法的时候,并在while循环中判断on 是否 == false 如果为false,就跳出while结束,即结束线程了

    八、线程间有多少通信方式

    java是基于共享内存的线程通信模型
    1、volatile和synchronized
    2、等待唤醒机制(wait/notify和await/signal)
        关于wait( ) 、notify( ) 以及notify( )使用时的细节
        (1)使用时需要先对调用的对象加锁,即被监控的对象
        (2)调用notify或notify方法后,等待线程不会立刻从wait返回,需要调用notify的线程释放锁后,等待线 程才有机会从wait返回 ,从wait()方法返回的前提是获得了调用对象的锁
    3、管道输入/输出流
    它与普通的文件输入输出流或网络输入输出流不同在于,它主要用于线程之间的数据传输,而传输媒介 为内存,管道需要进行绑定
    PiepeWriter out = new PipedWriter()
    PiepeReader in = new PipedReader()
    out. connect ( in );
    4、Thread的join方法
    是主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。
    5、TreadLocal线程变量

  • 相关阅读:
    范德蒙矩阵相关
    bat运行exe程序
    github 用token远程连接(三)
    为什么将样本方差除以N1?
    Git commit格式 详解(二)
    C++中this与*this的区别
    函数末尾加入const的作用
    git 使用小补充(四)
    人工智能 机器学习
    机器学习分类
  • 原文地址:https://www.cnblogs.com/zh-xiaoyuan/p/15259341.html
Copyright © 2020-2023  润新知