zoukankan      html  css  js  c++  java
  • 从Java线程到线程池

    线程模型

    线程模型分为两类,用户级线程(ULT)和内核级线程(KLT)

    • 用户级线程(ULT):user level threads,系统内核对ULT无感知,线程的创建和调度都由用户级APP进程管理;即APP自行管理的线程,就是用户级线程

    • 内核级线程(KLT):kernel level threads,线程的创建,调度和切换上下文等,都由操作系统内核管理

      如图所示,ULT模型中,每个进程创建的线程,均由自身进程维护管理,而KLT模型中,进程创建的线程,由系统内核进行维护管理。

    image-20200407095639121

    ​ 在执行的区别之上,ULT和KLT最大的区别就是:ULT线程的调度不需要内核直接参与,控制简单,创建和销毁线程、线程切换代价等线程管理的开销比内核线程少得多。但是其资源调度按照进程进行,不能利用系统的多核处理,多个处理器下,同一个进程中的线程只能在同一个处理器下分时复用;KLT由内核进行调度,当有多个处理器时,一个进程的多个线程可以同时执行,但是KLT线程的创建和切换等开销很大。

    ​ Java线程是依赖于系统内核,通过JVM调用系统库创建内核线程。Java的Thread和内核线程呈 1:1映射关系。by the way,这也是为什么我们需要线程池进行线程池化管理的原因。

    image-20200407095639121

    线程池

    基础概念

    ​ 线程是程序运行的载体,说白了就是拥有cpu多长时间的执行权。而由于java线程是内核级线程带来的昂贵开销成本,在平时运用场景中我们也经常使用线程池对线程进行池化管理。线程池能为我们带来3大好处:

    1. 复用已创建的线程,降低创建销毁线程等带来的资源消耗
    2. 提高响应速度。线程早已创建好,不需要在等待线程创建就可以直接使用
    3. 可管理线程。由于线程是稀有资源,不能无限创建,因此用线程池能对线程进行统一规划,监控和分配

    在线程池中,有几项概念最为重要:核心线程数,最大线程数,等待队列,饱和策略。它们的执行过程如下图。

    image-20200407234201366

    为加强记忆,可以用工厂干活的方式理解。把核心线程数看作是正式工,当来了一堆任务的时候,正式工陆续开始干活,当任务增多的时候,正式工们手头上的任务还没干完,于是先放阻塞队列等待待会儿处理;后来任务量暴涨,阻塞队列也满了,于是就请一些临时工来干活,于是,正式工数+临时工数 = 最大线程数;再后来线程数持续增长,最大线程数也满了,只能做一些饱和策略限制任务的提交了。

    线程池的使用

    ​ 我们可以通过new ThreadPoolExecutor的方式创建线程池。

    //构造方法
    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue)
      
    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory)
      
    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  RejectedExecutionHandler handler)
      
    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler)
    

    其中的参数需要说明下:

    corePoolSize:核心线程数。线程池在最初阶段,池中线程数为0,当接收到一个任务后,就创建一个线程。即使之前的线程执行完毕空闲,新任务提交过来,依然会创建线程。直到创建线程到核心线程数。如果调用了prestartAllCoreThreads方法,那么线程池会预先创建好线程。

    BlockingQueue: ⽤于保存等待执⾏的任务的阻塞队列,可参考JUC的7种阻塞队列,这里不再赘述

    maximumPoolSize:线程池允许创建的最⼤线程数。如果队列满了,并且已创建的线程数⼩于最⼤线程数,则线程池会再创建新的线程执⾏任务

    TimeUnit:线程活动保持时间的单位

    keepAliveTime:线程活动保持时间。当线程池中创建的线程数量超过核心线程数,且有线程空闲超过该时间,空闲线程将会被回收至核心线程数大小。当设置为0,即只要线程空闲就立即回收

    ThreadFactory:⽤于设置创建线程的⼯⼚,可以通过线程⼯⼚给每个创建出来的线程设置更有意义的名字。附录附上线程工厂的用法例子。

    RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取⼀种策略处理提交的新任务。这个策略默认情况下是AbortPolicy。在JDK1.5中Java线程池框架提供了以下4种策略。

    • AbortPolicy:直接抛出异常。

    • CallerRunsPolicy:当任务添加到线程池中被拒绝时,会在线程池当前正在运行的Thread线程池中处理被拒绝的任务。

    • DiscardOldestPolicy:线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。

    • DiscardPolicy:不处理,丢弃掉。

    当然也可以自定义,在附录举了一个demo可参考。

    举上一个较为完整的线程池创建示例

    BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(5);
    
    ThreadPoolExecutor threadPoolExecutor =
      new ThreadPoolExecutor(5, 10, 1000, TimeUnit.MILLISECONDS, workQueue, new ThreadPoolExecutor.AbortPolicy());
    

    线程池的工作状态

    线程池共有5种生命状态:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED

    image-20200408010817568
    • RUNNING:接受新任务并处理排队的任务

    • SHUTDOWN:不接受新任务,但处理排队的任务

    • STOP:不接受新任务,不处理排队的任务,中断正在进行的任务

    • TIDYING:所有任务都已终止,workerCount为零,将运行terminate()钩子方法

    • TERMINATED:线程池终止

    线程池提交任务的方式有两种,分别为execute()和 submit()⽅法。

    execute()⽅法⽤于提交不需要返回值的任务,⽆法判断任务是否被线程池执⾏成功。

    submit()⽅法⽤于提交需要返回值的任务。线程池会返回⼀个future类型的对象,可通过future对象判断线程是否执行成功,获取返回值等。

    线程池关闭的方式也有两种,shutdown()和shutdownNow()⽅法可关闭线程池。它们的原理是遍历线程池中的⼯作线程,然后逐个调⽤线程的interrupt⽅法来中断线程,所以⽆法响应中断的任务可能永远⽆法终⽌。

    区别在于,shutdownNow 更改状态成STOP,然后尝试停⽌所有的正在执⾏或暂停任务的线程,并返回等待执⾏任务的列表,⽽shutdown更改状态成SHUTDOWN状态,然后中断所有没有正在执⾏任务的线程,正在执行的线程会继续执行完毕。

    1.线程池设置多少合适:

    有大佬给出了详细计算模式
    https://dayarch.top/p/how-many-threads-should-be-created.html

    2. 线程工厂的demo

    /**
     * 实现线程工厂的接口,然后实现自定义内容。重写newThread方法
     */
    public class MyThreadFactory implements ThreadFactory {
    
        private static int COUNTER = 0;
    
        private static String THREAD_PREFIX = "myThread";
    
        @Override
        public Thread newThread(Runnable r) {
            int i = COUNTER++;
            return new Thread(r, THREAD_PREFIX + i);
        }
    
      	//使用
        public static void main(String[] args) {
            MyThreadFactory myThreadFactory = new MyThreadFactory();
            Thread thread = myThreadFactory.newThread(new Runnable() {
                @Override
                public void run() {
    
                }
            });
            thread.start();
        }
    }
    

    2. 自定义饱和策略

    //需要实现RejectedExecutionHandler,重写rejectedExecution方法
    class MyRejected implements RejectedExecutionHandler {
        public MyRejected() {
        }
    
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.out.println("现线程池中的情况为:" + executor.getActiveCount());
            System.out.println("当前被拒绝任务为:" + r.toString());
        }
    }
    
  • 相关阅读:
    浅拷贝在项目中的应用
    MVC3中使用AuthorizeAttribute特性来完成登陆权限的验证
    一个面向对象的JS例子,很好的支持了开闭原则(不要重复你昨天的代码)
    c++学习笔记5
    JS跨域访问操作iframe
    Select函数
    MacOS10.8.3+Xcode4.6+IOS6.1 编译FFmpeg,简单使用
    eclipse部署,在tomcat中找不到eclipse发布的项目。eclipse更改项目发布路径
    初识Volley(二)
    MySQL 5.0 迁移到 MariaDB 10.0.2 存储过程无法迁移
  • 原文地址:https://www.cnblogs.com/valjeanshaw/p/12658866.html
Copyright © 2011-2022 走看看