ThreadPool讲解

1.1获取线程的方法

  • 实现Runnable接口
  • 实现Callable接口
  • 实例化Thread类
  • 使用线程池获取

1.2为什么用线程池

线程池主要工作是控制运行的线程的数量,处理过程中,将任务放入到队列中,然后线程创建后,启动这些任务,如果线程数量超过了最大数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。

1.2.1 它的主要特点为:线程复用、控制最大并发数、管理线程

1.2.2 线程池中的任务是放入到阻塞队列中的

1.2.3线程池的好处

  • 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就立即执行
  • 提高线程的可管理性。线程是稀缺资源,如果无线创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

1.3架构说明

Java中线程池是通过Executor框架实现的,该框架中用到了Executor,Executors(代表工具类),ExecutorService,ThreadPoolExecutor这几个类。

 

1.4创建线程池

1.4.1常用线程池创建

(1)Executors.newFixedThreadPool(int i) :创建一个拥有 i 个线程的线程池

  • 执行长期的任务,性能好很多
  • 创建一个定长线程池,可控制线程数最大并发数,超出的线程会在队列中等待

(2)Executors.newSingleThreadExecutor:创建一个只有1个线程的 单线程池

  • 一个任务一个任务执行的场景
  • 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行

(3)Executors.newCacheThreadPool(); 创建一个可扩容的线程池

  • 执行很多短期异步的小程序或者负载教轻的服务器
  • 创建一个可缓存线程池,如果线程长度超过处理需要,可灵活回收空闲线程,如无可回收,则新建新线程

(4)Executors.newScheduledThreadPool(int corePoolSize)创建定时以及周期性执行任务

  • 创建一个指定线程数定时周期执行任务

1.4.2创建实例

package com.tool.controller;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName ThreadPoolDemo
 * @Date 2021/11/22 11:31
 * @Author shich
 * @Description 线程池实现四种方式
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        //fixedThreadPool();// 一池5个处理线程(用池化技术,一定要记得关闭)
        //singleThreadExecutor(); // 创建一个只有一个线程的线程池
        //cachedThreadPool();  //创建一个拥有N个线程的线程池,根据调度创建合适的线程
        //scheduledThreadPool();//创建定时以及周期性执行任务
    }
    /**
     * @MethodName: scheduledThreadPool
     * @Description:  一池5个处理线程(用池化技术,一定要记得关闭)
     * @Author: shich
     * @Date: 2021/11/22 13:50
     * @param :
     * @return void
     */
    public static void fixedThreadPool(){
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        executeThreadPool(threadPool);
    }
    /**
     * @MethodName: singleThreadExecutor
     * @Description:  创建一个只有一个线程的线程池
     * @Author: shich
     * @Date: 2021/11/22 13:50
     * @param :
     * @return void
     */
    public static void singleThreadExecutor(){
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        executeThreadPool(threadPool);
    }
    /**
     * @MethodName: cachedThreadPool
     * @Description:  创建一个拥有N个线程的线程池,根据调度创建合适的线程
     * @Author: shich
     * @Date: 2021/11/22 13:50
     * @param :
     * @return void
     */
    public static void cachedThreadPool(){
        ExecutorService threadPool = Executors.newCachedThreadPool();
        executeThreadPool(threadPool);
    }
/**
 * @MethodName: executeThreadPool
 * @Description: 执行逻辑
 * @Author: shich
 * @Date: 2021/11/22 13:59
 * @param threadPool:
 * @return void
 */
    public static void executeThreadPool(ExecutorService threadPool){
        try {
            for (int i = 0; i < 10; i++) {
                final int tempInt = i;
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }

    /**
     * @MethodName: scheduledThreadPool
     * @Description:  创建定时以及周期性执行任务
     * @Author: shich
     * @Date: 2021/11/22 13:50
     * @param :
     * @return void
     */
    public static void scheduledThreadPool(){
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int tempInt = i;
            scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务");
                }
            },0, 4, TimeUnit.SECONDS);
        }
    }

}


    }
}

1.5底层实现

1.5.1我们通过查看源码,点击了Executors. newCacheThreadPool能够发现底层都是使用了ThreadPoolExecutor

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;
}

线程池在创建的时候,一共有7大参数

(1)corePoolSize:核心线程数,线程池中的常驻核心线程数

  • 在创建线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程
  • 当线程池中的线程数目达到corePoolSize后,就会把到达的队列放到缓存队列中

(2)maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1、

  • 相当有扩容后的线程数,这个线程池能容纳的最多线程数

(3)keepAliveTime:多余的空闲线程存活时间

  • 当线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余的空闲线程会被销毁,直到只剩下corePoolSize个线程为止
  • 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用

(4)unit:keepAliveTime的单位

(5)workQueue:任务队列,被提交的但未被执行的任务

  • arrayBlockingQueue 数组阻塞队列
  • LinkedBlockingQueue:链表阻塞队列
  • SynchronousBlockingQueue:同步阻塞队列

(6)threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程池 一般用默认即可

(7)handler:拒绝策略,表示当队列满了并且工作线程大于线程池的最大线程(maximumPoolSize3)时,如何来拒绝请求执行的Runnable的策

1.5.2拒绝策略

(1)以下所有拒绝策略都实现了RejectedExecutionHandler接口

  • AbortPolicy:默认,直接抛出RejectedExcutionException异常,阻止系统正常运行
  • DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常,如果运行任务丢失,这是一种好方案
  • CallerRunsPolicy:该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
  • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务

1.6线程池底层工作原理

1.6.1线程池运行架构图

1.6.2字说明

1)在创建了线程池后,等待提交过来的任务请求

2)当调用execute()方法添加一个请求任务时,线程池会做出如下判断

  • 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
  • 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
  • 如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
  • 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

3)当一个线程完成任务时,它会从队列中取下一个任务来执行。

4)当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:

  • 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉
  • 所以线程池的所有任务完成后,它会最终收缩到corePoolSize的大小

1.7为什么不用默认创建的线程池?

线程池创建的方法有:固定数的,单一的,可变的,那么在实际开发中,应该使用哪个?我们一个都不用,在生产环境中是使用自己自定义的为什么不用 Executors 中JDK提供的?

根据阿里巴巴手册:并发控制这章

1线程资源必须通过线程池提供,不允许在应用中自行显式创建线程

  • 使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题,如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题

2线程池不允许使用Executors去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

  • FixedThreadPool和SingleThreadPool:运行的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
  • CacheThreadPool和ScheduledThreadPool运行的请求线程数:Integer.MAX_VALUE,线程数上限太大导致oom

1.8手写线程池

1.8.1拒绝策略

我们要使用ThreadPoolExecutor自己手动创建线程池,然后指定阻塞队列的大小下面我们创建了一个 核心线程数为2,最大线程数为5,并且阻塞队列数为3的线程池

package com.tool.controller;

import java.util.concurrent.*;

/**
 * @ClassName TreadPoolRejectDemo
 * @Date 2021/11/22 15:15
 * @Author shich
 * @Description 拒绝策略演示
 */
public class TreadPoolRejectDemo {
    public static void main(String[] args) {
        //doSomething(initThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.AbortPolicy()), 10);//中断异常
        //doSomething(initThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.CallerRunsPolicy()), 10);//回退调度
        //doSomething(initThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.DiscardOldestPolicy()), 10);//丢弃最久未执行任务
        //doSomething(initThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.DiscardPolicy()), 10);//直接丢弃任务
        //doSomething(initThreadPoolExecutor(2, 5, 3, new RejectedExecutionHandlerDemo()), 10);//直接丢弃任务
    }
    /**
     * @MethodName: doSomething
     * @Description: 执行逻辑
     * @Author: shich
     * @Date: 2021/11/22 14:43
     * @param executorService:
     * @param numOfRequest:
     * @return void
     */
    public static void doSomething(ExecutorService executorService, int numOfRequest) {

        try {

            System.out.println(((ThreadPoolExecutor)executorService).getRejectedExecutionHandler().getClass() + ":");
            TimeUnit.SECONDS.sleep(1);
            for (int i = 0; i < numOfRequest; i++) {
                final int tempInt = i;
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务");
                });
            }
            TimeUnit.SECONDS.sleep(1);
            System.out.println("\n\n");

        } catch (Exception e) {
            System.err.println(e);
        } finally {
            executorService.shutdown();
        }
    }
    /**
     * @MethodName: initMyThreadPoolExecutor
     * @Description: 手写初始化线程池
     * @Author: shich
     * @Date: 2021/11/22 14:45
     * @param corePoolSize:
     * @param maximumPoolSize:
     * @param blockingQueueSize:
     * @param handler:
     * @return java.util.concurrent.ExecutorService
     */
    public static ExecutorService initThreadPoolExecutor(int corePoolSize,
                                                         int maximumPoolSize,
                                                         int blockingQueueSize,
                                                         RejectedExecutionHandler handler){
        return new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                1,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(blockingQueueSize),
                new ThreadFactoryDemo(),
                handler);
    }

}

1.8.2自定义拒绝策略

package com.tool.controller;

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @ClassName RejectedExecutionHandlerDemo
 * @Date 2021/11/23 9:20
 * @Author shich
 * @Description TODO
 */
public class RejectedExecutionHandlerDemo implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        try {
            //blockingqueue put阻塞方法
            executor.getQueue().put(r);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

涉及到阻塞队列,如下提供阻塞队列api

package com.tool.controller;


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
 * @ClassName BlockingQueueDemo
 * @Date 2021/11/23 15:39
 * @Author shich
 * @Description TODO
 */
public class BlockingQueueDemo {

    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        //队列插入数据
        new Thread(()->{
            try {
                blockingQueue.put("a");
                blockingQueue.put("b");
                blockingQueue.put("c");
                blockingQueue.put("c");//将会阻塞,直到主线程take()
                System.out.println("it was blocked.");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        //队列取数据
        try {

            TimeUnit.SECONDS.sleep(2);
            System.out.println(blockingQueue.take());
            System.out.println(blockingQueue.take());
            System.out.println(blockingQueue.take());
            System.out.println(blockingQueue.take());
            System.out.println("Blocking...");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

1.9 手写线程池工厂

package com.tool.controller;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ClassName ThreadFactoryDemo
 * @Date 2021/11/23 9:35
 * @Author shich
 * @Description TODO
 */
public class ThreadFactoryDemo implements ThreadFactory {
    private AtomicInteger count = new AtomicInteger(0);

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        String threadName = ThreadFactoryDemo.class.getSimpleName() + count.addAndGet(1);
        t.setName(threadName);
        return t;
    }
}

2.0线程池的合理参数

1.9.1生产环境中如何配置这个是根据具体业务来配置的,分为CPU密集型和IO密集型

(1)CPU密集型

  • CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行,尽可能配置少
  • 一般公式:CPU核数 + 1个线程数,+1是调度过程有线程中断,可以充分利用cpu,容错

(2)IO密集型

  • 由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数 * 2
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值