20210728Java线程池

20210728Java线程池

编辑时间:2021/07/28

读完本节:大概花费不到20分钟,共2469词

1.使用线程池的优点
  1. 降低资源销毁

    通过重复利用已经创建的线程,降低线程创建和销毁造成的消耗

  2. 提高响应速度

    当任务到达时,任务可以不需要等到线程创建就能立即执行

  3. 防止服务器过载

    形成内存溢出,或者CPU耗尽。

  4. 提高线程的可管理性

    线程是稀缺资源,如果无限制地创建,不仅会消耗资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。

2.线程池的状态
  1. SHUT DOWN

    表示不接受新任务,但可执行队列中的任务

  2. TIDYING

    所有任务已经中止,且工作线程数量为0

  3. RUNNING

    表示可接受新任务,且可执行队列中的任务

  4. STOP

    不接受新任务,且不再执行队列中的任务,且中断正在执行的任务

  5. TERMINATED

    中止状态

  6. 线程池的状态有限机

    截屏2021-07-28 上午1.21.59

3.线程池的分类
  1. ThreadPoolExecutor

    1. ThreadPoolExecutor的继承关系:

      ThreadPoolExecutor继承于AbstractExecutorService,AbstractExecutorService继承于ExecutorService,ExecutorService继承于Executor

    2. ThreadPoolExecutor的其中一个构造方法

      /**
      * Creates a new {@code ThreadPoolExecutor} with the given initial
      * parameters.
      *
      * @param corePoolSize the number of threads to keep in the pool, even
      *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
      * @param maximumPoolSize the maximum number of threads to allow in the
      *        pool
      * @param keepAliveTime when the number of threads is greater than
      *        the core, this is the maximum time that excess idle threads
      *        will wait for new tasks before terminating.
      * @param unit the time unit for the {@code keepAliveTime} argument
      * @param workQueue the queue to use for holding tasks before they are
      *        executed.  This queue will hold only the {@code Runnable}
      *        tasks submitted by the {@code execute} method.
      * @param threadFactory the factory to use when the executor
      *        creates a new thread
      * @param handler the handler to use when execution is blocked
      *        because the thread bounds and queue capacities are reached
      * @throws IllegalArgumentException if one of the following holds:<br>
      *         {@code corePoolSize < 0}<br>
      *         {@code keepAliveTime < 0}<br>
      *         {@code maximumPoolSize <= 0}<br>
      *         {@code maximumPoolSize < corePoolSize}
      * @throws NullPointerException if {@code workQueue}
      *         or {@code threadFactory} or {@code handler} is null
      */
      public ThreadPoolExecutor(int corePoolSize,
                                    int maximumPoolSize,
                                    long keepAliveTime,
                                    TimeUnit unit,
                                    BlockingQueue<Runnable> workQueue,
                                    ThreadFactory threadFactory,
                                    RejectedExecutionHandler handler) {
      	if (corePoolSize < 0 ||
                  maximumPoolSize <= 0 ||
                  maximumPoolSize < corePoolSize ||
                  keepAliveTime < 0)
                  throw new IllegalArgumentException();
      	if (workQueue == null || threadFactory == null || handler == null)
                  throw new NullPointerException();
      	this.acc = System.getSecurityManager() == null ?
      						null :
      						AccessController.getContext();
      	this.corePoolSize = corePoolSize;
      	this.maximumPoolSize = maximumPoolSize;
      	this.workQueue = workQueue;
      	this.keepAliveTime = unit.toNanos(keepAliveTime);
      	this.threadFactory = threadFactory;
      	this.handler = handler;
      }
      
    3. 构造方法中型参的作用

      corePoolSize:线程池中的核心线程数量,在没有用的时候,也不会被回收。

      maximumPoolSize:就是线程池中可以容纳的最大线程的数量

      keepAliveTime:线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间

      util:计算时间的一个单位

      workQueue:就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)

      handler:拒绝策略

      threadFactory:一个用于创建新线程的工厂

    4. 线程池一个任务的提交过程

      截屏2021-07-28 上午1.51.07

    5. 线程池的拒绝策略

      如何触发拒绝策略:当任务队列满了之后,如果还有任务提交过来,就会触发拒绝策略

      四种常用的拒绝策略:

      pAbortPolicy:不执行新任务,直接抛出异常,提示线程池已满,默认该方式。

      pCallerRunsPolicy:直接调用execute来执行当前任务。

      pDiscardPolicy:丢弃任务,但是不抛出异常。

      pDiscardOldestPolicy:抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。先从任务队列中弹出最先加入的任务,空出一个位置,然后再次执行execute方法把任务加入队列。

    6. 四种常用的线程池

      1. newCachedThreadPool

        创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

        底层源码实际上是new了一个ThreadPoolExecutor,它没有核心线程,最大线程可以有多个,然后保活60秒,60秒内无使用,就被回收。它的任务队列用的是SynchronousQueue。没有指定它的线程工厂,它的线程工厂用的是默认的线程工厂。也没有拒绝策略,使用默认拒绝策略。

        newCachedThreadPool的特点:一旦收到新任务就必须马上执行,没有线程空着就new线程,这样会导致启动的线程特别多,基本没有上限,导致系统开销大,但是能够迅速响应线程执行的要求

        使用举例

        ExecutorService pool = Executors.newCachedThreadPool();
        
        for(int i = 0; i < 10; i++){
          	Thread.sleep(200);
        		pool.execute(()->{
              	for(int s = 0; s < 10; s++){
                  	System.out.println(Thread.currentThread.getName() + ":" s);
                  	try{
                      	Thread.sleep(200);
                    }catch(InterruptedException e){
                      	e.printStackTrace();
                    }
                }
            });  
        }
        
      2. newFixedThreadPool

        创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

        FixedThreadPool到底有几个线程是由核心线程数和最大线程数确定的。底层源码

        /**
        * Creates a thread pool that reuses a fixed number of threads
        * operating off a shared unbounded queue.  At any point, at most
        * {@code nThreads} threads will be active processing tasks.
        * If additional tasks are submitted when all threads are active,
        * they will wait in the queue until a thread is available.
        * If any thread terminates due to a failure during execution
        * prior to shutdown, a new one will take its place if needed to
        * execute subsequent tasks.  The threads in the pool will exist
        * until it is explicitly {@link ExecutorService#shutdown shutdown}.
        *
        * @param nThreads the number of threads in the pool
        * @return the newly created thread pool
        * @throws IllegalArgumentException if {@code nThreads <= 0}
        */
        public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
        

        底层实际还是使用ThreadPoolExecutor创建线程池,但是corePoolSize大小和maximumPoolSize都被确定了,也因为如此不需要进行回收,所以保活时间keepAliveTime设置为0L。

        newFixedThreadPool使用举例

        ExecutorService pool = Executors.newFixedThreadPool(2);
        
        for(int i = 0; i < 10; i++){
          	Thread.sleep(200);
        		pool.execute(()->{
              	for(int s = 0; s < 10; s++){
                  	System.out.println(Thread.currentThread.getName() + ":" s);
                  	try{
                      	Thread.sleep(200);
                    }catch(InterruptedException e){
                      	e.printStackTrace();
                    }
                }
            });  
        }
        
      3. newScheduledThreadPool

        创建一个定长线程池,支持定时及周期性任务执行。

        newScheduledThreadPool使用举例

        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
        int t = 0;
        pool.execute(()->{
          	System.out.println(
            		Thread.currentThread.getName() + ":" + t,
            		0,
            		2,
            		TimeUnit.SECONDS);
        });
        
      4. newSingleThreadExecutor

        创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

        newSingleThreadExecutor使用举例

        ExecutorService pool = Executors.newSingleThreadExecutor();
        
        for(int i = 0; i < 10; i++){
          	Thread.sleep(200);
        		pool.execute(()->{
              	for(int s = 0; s < 10; s++){
                  	System.out.println(Thread.currentThread.getName() + ":" s);
                  	try{
                      	Thread.sleep(200);
                    }catch(InterruptedException e){
                      	e.printStackTrace();
                    }
                }
            });  
        }
        
  2. ForkJoinPool

    思想:将一个任务划分成若干个子任务,然后分别在多线程的情况下运行,最后汇总结果并输出

    使用ThreadPoolExecutor定义任务的时候是从Runnable来继承,在实现ForkJoinPool的时候需要定义称谓特定的task类型,这个类型必须得能进行分叉的任务,所以将这个task定义成是一种特殊类型的任务,叫做ForkJoinTask。实际上,这个ForkJoinTask比较原始,可以用RecursiveAction。RecursiveAction其中一种叫RecursiveAction递归,称为递归的原因是:大任务分解成小任务,一直可以切到满足条件为止,这其中隐含了一个递归过程,因此叫RecursiveAction,这个RecursiveAction是不带返回值的任务。

4.如何调整线程池的大小
  1. 如果线程池中的线程数量过多,最终他们会竞争稀缺的处理器和内存资源,浪费大量的时间在上下文切换上,反之,如果线程池的数目过少,导致一些核可能无法充分利用。线程池的大小与处理器的利用率之比可以使用下面的公式进行估算

Nthread = NCPU * UCPU * (1 + W/C)

其中

NCPU是处理器的核心数目,可以通过Runtime.getRuntime().availableProcessors()获取

UCPU是期望的CPU利用率,这个值介于0和1之间

W/C是等待时间与计算时间的比率

5.线程池的使用情景
  1. 高并发、任务执行时间短的业务怎样使用线程池?

    高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换

  2. 并发不高、任务执行时间长的业务怎样使用线程池?

    假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务。

    假如是业务时间长集中在计算操作上,也就是计算密集型任务,线程池中的线程数设置得少一些,减少线程上下文的切换

6.线程池的提交方式
  1. submit

    1. submit既可以提交Runnable类型的任务,也可以提交Callable类型的任务,会有一个类型为Future的返回值,但当任务类型为Runnable时,返回值为null。

    2. submit不会直接抛出,只有在使用Future的get方法获取返回值时,才会抛出异常

    3. 使用

      Executor pool = Executors.newFixedThreadPool(10);
      
      //runnable 拉姆达表达式,包含返回值
      Future<?> submit = pool.submit(()->{
        	System.out.printn(
          		Thread.currentThread().getName() + ">>> test submit..."
          );
      });
      
  2. execute

    1. execute只能提交Runnable类型的任务,无返回值。

    2. execute在执行任务时,如果遇到异常会直接抛出

    3. 使用

      Executor pool = Executors.newFixedThreadPool(10);
      
      //runnable 拉姆达表达式
      pool.execute(()->{
        	System.out.printn(
          		Thread.currentThread().getName() + ">>> test execute..."
          );
      });
      
无限进步
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值