JUC包的线程池详解

编程之家收集整理的这篇文章主要介绍了JUC包的线程池详解编程之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

为什么要使用线程池

  • 创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程

  • 控制并发的数量。并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃。(主要原因)

  • 可以对线程做统一管理

JUC下线程池的体系结构

image-20210424161015485

创建线程池的两种方法

  1. 使用ThreadPoolExecutor的构造方法创建

    public class ThreadPoolTest1 {
    
        public static void main(String[] args) {
    
            ThreadPoolExecutor pool = new ThreadPoolExecutor(5,8,1,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
    
            for (int i = 0; i < 11; i++) {
                pool.execute(
                        () -> {                  System.out.println(Thread.currentThread().getName());
                        }
                );
    
            }
        }
    

    image-20210424162737796

  2. 使用Executors这个工具类来实现

    JDK工具类为我们提供了四种常用的线程池,其实它们的底层源码都是调用ThreadPoolExecutor来实现的,传递的线程池参数不同罢了。

    四种常见的线程池

    工程中我们都是使用第一种方法来创建线程池,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的⻛险(OOM)

线程池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;
 }
  • corePoolSize:核心线程的最大值,相当于正式员工,到点才下班
  • maximumPoolSize:核心线程+非核心线程的最大值,非核心线程相当于临时工,核心线程处理不过来才会激活非核心线程,业务量低时先下班
  • keepAliveTime:非核心线程闲置超时时长,超时了非核心线程被销毁
  • TimeUnit unit:keepAliveTime的时间单位
  • workQueue:阻塞队列,线程池的底层也用了阻塞队列,维护等待执行的线程对象
  • ThreadFactory threadFactory:创建线程的工程,一般使用Excetory的默认实现默认实现
  • RejectedExecutionHandler handler:阻塞队列满了之后对新来线程的拒绝策略,默认有四种
    1. ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛出RejectedExecutionException异常。
    2. ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,但是不抛出异常。
    3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列头部(最旧的)的任务,然后重新尝试执行程序(如果再次失败,重复此过程)。
    4. ThreadPoolExecutor.CallerRunsPolicy:返回给上一步,由调用线程处理该任务。

线程池调度的策略

image-20210424164526903

线程池调度的核心是execute方法,总结完就是上图

// JDK 1.8 
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();   
    int c = ctl.get();
    // 1.当前线程数小于corePoolSize,则调用addWorker创建核心线程执行任务
    if (workerCountOf(c) < corePoolSize) {
       if (addWorker(command,true))
           return;
       c = ctl.get();
    }
    // 2.如果不小于corePoolSize,则将任务添加到workQueue队列。
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 2.1 如果isRunning返回false(状态检查),则remove这个任务,然后执行拒绝策略。
        if (! isRunning(recheck) && remove(command))
            reject(command);
            // 2.2 线程池处于running状态,但是没有线程,则创建线程
        else if (workerCountOf(recheck) == 0)
            addWorker(null,false);
    }
    // 3.如果放入workQueue失败,则创建非核心线程执行任务,
    // 如果这时创建非核心线程失败(当前线程总数不小于maximumPoolSize时),就会执行拒绝策略。
    else if (!addWorker(command,false))
         reject(command);
}

这个对线程调度的源码没有深入分析,如addWord函数,拒绝策略是怎么实现的等。以后会再专门写一篇文章,可以参考《并发编程之美》的源码分析。

执行execute方法和submit方法有何区别?

  1. execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
  2. submit()方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功,并且可以通过Future的get()方法获取返回值。

这也可以看到线程池的又一优点:灵活。

参考

总结

以上是编程之家为你收集整理的JUC包的线程池详解全部内容,希望文章能够帮你解决JUC包的线程池详解所遇到的程序开发问题。

如果觉得编程之家网站内容还不错,欢迎将编程之家网站推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。

小编个人微信号 jb51ccc
喜欢与人分享编程技术与工作经验,欢迎加入编程之家官方交流群!

相关文章

猜你在找的Java相关文章

什么是线程不安全 我对线程安全的理解就是多个线程同时操作一个共享变量时会产生意料之外的情况,这种情况就是线程不安全。注意:只有写操作才可能出现线程不安全,对共享变量只进行读操作线程是绝对安全的。 具体
为什么要使用线程池 创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程。 控制并发的数量。并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃。(主要原因) 可以对线程做统一管理。 JUC
参考资料: JVM虚拟机入门教程-陈树义 JavaGuide 《深入理解Java虚拟机》 面经: 个人整理 - Java 后端面试题 - JVM 篇 阿里 Java 实习十轮面试面经 1. 讲一下JV
代码如下:/*  练习把一个整数逆序输出  分别把个位,十位,百位,千位等各位的数字取出来*/import java.util.Scanner;class Demo18 { public static void main(String[] args)  { Scanner sc=new Scanner(System.in); System.out.println(&quot;请输入:&quot;); int n...
代码如下:/*  练习限制用户登陆的次数*/import java.util.Scanner;class Demo16 { public static void main(String[] args)  { Scanner sc=new Scanner(System.in); String name=&quot;&quot;; String pwd=&quot;&quot;; for(int i=1;i&amp;lt;=5;i++){ ...
代码如下:/*  从键盘上输入正数和附属,分别统计正数和负数的个数并计算所有数的和。输入0表示结束*/import java.util.Scanner;class Demo15{ public static void main(String[] args)  { Scanner sc=new Scanner(System.in); int num=1,t=0,f=0,sum=0; while...
代码如下:/*  多分支练习彩票游戏,随机生成两个一位数的随机数,提示用户输入猜测的数字  如果完全匹配奖金10000,只匹配数字没有匹配顺序奖金3000,只匹配一个数字奖金1000  例:若生成的随机数为18,如果用户输入18奖金10000;如果用户输入81奖金3000;如果用户输入16奖金1000*/import java.util.Scanner;class Demo13{ public s...
代码如下:/*  练习简易计算器*/import java.util.Scanner;class Demo12 { public static void main(String[] args)  { Scanner sc=new Scanner(System.in); System.out.println(&quot;请输入第一个数字:&quot;); int a=sc.nextInt(); System.o...