带着面试官畅游Java线程池

简介: java中经常需要用到多线程来处理一些业务,如果单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。同时创建过多的线程也可能引发资源耗尽的风险,这个时候引入线程池比较合理,方便线程任务的管理。

一、为什么使用线程池


     

java中经常需要用到多线程来处理一些业务,如果单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。同时创建过多的线程也可能引发资源耗尽的风险,这个时候引入线程池比较合理,方便线程任务的管理。


java中涉及到线程池的相关类均在jdk1.5开始的 java.util.concurrent(JUC)包中,涉及到的几个核心类及接口包括:Executor、Executors、ExecutorService、ThreadPoolExecutor、FutureTask、Callable、Runnable等。


二、线程池的优势


     

总体来说,线程池有如下的优势:


(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

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


三、线程池的简单使用


   

1、Executors.newCachedThreadPool()

   

cached的池子非常大,可以有很多的线程并发运行。


package threadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo01 {
  public static void main(String[] args) {
    ExecutorService e1 = Executors.newCachedThreadPool();
//    ExecutorService e2 = Executors.newFixedThreadPool(10);
//    ExecutorService e3 = Executors.newSingleThreadExecutor();
    //使用线程
    e1.execute(new Task(1));
  }
}
class Task implements Runnable{
  int i;
  public Task(int i) {
    this.i = i;
  }
  @Override
  public void run() {
    //打印当前线程的名字
    System.out.println(Thread.currentThread().getName() + "-----" + i);
  } 
}

cdc37605d33b47379f03b4e152dbd4cb.png


已经打出了当前线程的名字。


我们将调用线程的方法执行100次,由于run方法执行有时长,线程来不及回池子时就需要再执行,所以需要开启另一个线程。执行100次大概需要开启三十多个线程。


public class ThreadPoolDemo01 {
  public static void main(String[] args) {
  ExecutorService e1 = Executors.newCachedThreadPool();
//  ExecutorService e2 = Executors.newFixedThreadPool(10);
//  ExecutorService e3 = Executors.newSingleThreadExecutor();
  //使用线程
  for(int i=0; i<100; i++) {
    e1.execute(new Task(i));
  }
  }
}
class Task implements Runnable{
  int i;
  public Task(int i) {
  this.i = i;
  }
  @Override
  public void run() {
  //打印当前线程的名字
  System.out.println(Thread.currentThread().getName() + "-----" + i);
  } 
}


22c040b916e249d4b0a123e3ae250119.png


线程池的序号大概是到三十几,说明从线程池中拿了三十多个线程。


我们在run方法中加一个一秒的睡眠,再看看结果。


class Task implements Runnable{
  int i;
  public Task(int i) {
    this.i = i;
  }
  @Override
  public void run() {
    //打印当前线程的名字
    System.out.println(Thread.currentThread().getName() + "-----" + i);
    //睡1s
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  } 
}

77b580b3a55343f68401ab224d88171c.png



可以看到大概使用了100个线程。说明每一次调用都需要用一个新的线程。


2、Executors.newFixedThreadPool()

     

fixed线程池需要指定生成的线程数量。我们在代码中指定生成10个线程。


线程池中只会生成10个线程,如果run方法中睡1秒钟,那么在1s之内只能打印出10个线程名字,我们的代码执行完需要10s。


public class ThreadPoolDemo01 {
  public static void main(String[] args) {
//    ExecutorService e1 = Executors.newCachedThreadPool();
    ExecutorService e2 = Executors.newFixedThreadPool(10);
//    ExecutorService e3 = Executors.newSingleThreadExecutor();
    //使用线程
    for(int i=0; i<100; i++) {
      e2.execute(new Task(i));
    }
  }
}
class Task implements Runnable{
  int i;
  public Task(int i) {
    this.i = i;
  }
  @Override
  public void run() {
    //打印当前线程的名字
    System.out.println(Thread.currentThread().getName() + "-----" + i);
    //睡1s
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  } 
}

线程池中只会生成10个线程,如果run方法中睡1秒钟,那么在1s之内只能打印出10个线程名字,我们的代码执行完需要10s。

35fd607d26534b589c4dd97f7daba399.png


3、Executors.newSingleThreadExecutor()


这个线程池中只有一个线程,可以理解为fixed的单数版。


public class ThreadPoolDemo01 {
  public static void main(String[] args) {
//  ExecutorService e1 = Executors.newCachedThreadPool();
//  ExecutorService e2 = Executors.newFixedThreadPool(10);
  ExecutorService e3 = Executors.newSingleThreadExecutor();
  //使用线程
  for(int i=0; i<100; i++) {
    e3.execute(new Task(i));
  }
  }
}
class Task implements Runnable{
  int i;
  public Task(int i) {
  this.i = i;
  }
  @Override
  public void run() {
  //打印当前线程的名字
  System.out.println(Thread.currentThread().getName() + "-----" + i);
  //睡1s
  try {
    Thread.sleep(1000);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  } 
}


1bf89cb8c00c41808b3acd7469b4bc91.png

每次打印出的线程名字都相同,说明自始至终都使用的用一个线程。1s钟只打印1个名字,代码执行完需要100s(需要等1s后线程回到线程池,才能再使用这个线程)。


四、线程池源码分析



public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
可以看到他们都是由ThreadPoolExecutor构造出的方法,那我们再看看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;
    }


1、线程池参数


int corePoolSize     核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务


int maximumPoolSize   最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)


long keepAliveTime   线程的最大存活时间,空闲时间超过keepAliveTime就会被自动终止回收掉,一直回收到剩corePoolSize个


TimeUnit unit       存活时间的单位


BlockingQueue<Runnable> workQueue              阻塞队列


ThreadFactory threadFactory                               线程工厂


RejectedExecutionHandler handler                      拒绝执行时的处理函数


按照下面的代码来执行一下


public class ThreadPoolDemo01 {
  public static void main(String[] args) {
//  ExecutorService e1 = Executors.newCachedThreadPool();
//  ExecutorService e2 = Executors.newFixedThreadPool(10);
//  ExecutorService e3 = Executors.newSingleThreadExecutor();
  ThreadPoolExecutor t = new ThreadPoolExecutor(
            10,   //corePoolSize
            20,   //maximumPoolSize
            10,   //keepAliveTime
            TimeUnit.SECONDS,  //TimeUnit 
            new ArrayBlockingQueue<>(10)  //BlockingQueue
            );
  //使用线程
  for(int i=0; i<100; i++) {
    t.execute(new Task(i));
  }
  }
}
class Task implements Runnable{
  int i;
  public Task(int i) {
  this.i = i;
  }
  @Override
  public void run() {
  //打印当前线程的名字
  System.out.println(Thread.currentThread().getName() + "-----" + i);
  //睡1s
  try {
    Thread.sleep(1000);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  } 
}

     

在代码中,我们将核心线程数设置为10,最大线程数设置为20,最大存活时间设置为10,单位为秒,阻塞队列的大小设置为10


看一下执行结果:


pool-1-thread-3-----2
pool-1-thread-7-----6
pool-1-thread-1-----0
pool-1-thread-2-----1
pool-1-thread-4-----3
pool-1-thread-5-----4
pool-1-thread-6-----5
pool-1-thread-10-----9
pool-1-thread-9-----8
pool-1-thread-8-----7
pool-1-thread-11-----20
pool-1-thread-12-----21
pool-1-thread-13-----22
pool-1-thread-14-----23
pool-1-thread-15-----24
pool-1-thread-16-----25
pool-1-thread-17-----26
pool-1-thread-18-----27
pool-1-thread-19-----28
pool-1-thread-20-----29
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task threadPool.Task@135fbaa4 rejected from java.util.concurrent.ThreadPoolExecutor@45ee12a7[Running, pool size = 20, active threads = 20, queued tasks = 10, completed tasks = 0]
  at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor.reject(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)
  at threadPool.ThreadPoolDemo01.main(ThreadPoolDemo01.java:27)
pool-1-thread-16-----11
pool-1-thread-15-----12
pool-1-thread-13-----15
pool-1-thread-19-----10
pool-1-thread-14-----13
pool-1-thread-8-----18
pool-1-thread-11-----17
pool-1-thread-12-----16
pool-1-thread-18-----14
pool-1-thread-9-----19


2、执行流程分析


由于我们的核心线程数(corePoolSize)设置为10,就会有10个常驻的核心线程去执行


pool-1-thread-3-----2
pool-1-thread-7-----6
pool-1-thread-1-----0
pool-1-thread-2-----1
pool-1-thread-4-----3
pool-1-thread-5-----4
pool-1-thread-6-----5
pool-1-thread-10-----9

     

由于run方法中睡眠了一秒钟,后面进入的任务会进入阻塞队列(blockingQueue)中,当阻塞队列中的十个空间被填满后,创建普通的线程去执行。


pool-1-thread-11-----20
pool-1-thread-12-----21
pool-1-thread-13-----22
pool-1-thread-14-----23
pool-1-thread-15-----24
pool-1-thread-16-----25
pool-1-thread-17-----26
pool-1-thread-18-----27
pool-1-thread-19-----28
pool-1-thread-20-----29

     

由于blockingQueue的大小为10,10个任务进去之后,再进任务就会报拒绝执行(RejectedExecutionException)的错了:


Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task threadPool.Task@135fbaa4 rejected from java.util.concurrent.ThreadPoolExecutor@45ee12a7[Running, pool size = 20, active threads = 20, queued tasks = 10, completed tasks = 0]
  at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor.reject(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)
  at threadPool.ThreadPoolDemo01.main(ThreadPoolDemo01.java:27)

     

1秒之后,线程执行结束之后回到线程池,就可以继续去队列中接受任务。将队列中的十个任务接收完。


核心线程和普通线程是不作区分的,他们没有任何的区别,所以接收任务的时候也是谁先结束谁就去接收。


pool-1-thread-16-----11
pool-1-thread-15-----12
pool-1-thread-13-----15
pool-1-thread-19-----10
pool-1-thread-14-----13
pool-1-thread-8-----18
pool-1-thread-11-----17
pool-1-thread-12-----16
pool-1-thread-18-----14
pool-1-thread-9-----19

     

3、cachedThreadPool()源码分析


public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

     

可以看到在cached中,核心线程数为0,线程总数为无穷大,阻塞队列为0,线程存活时间为60s。


这说明cached中没有核心线程,任务也不能进入阻塞队列,那么在一开始就会申请普通线程去执行。而线程存活时间为60s,被复用的次数会非常多,除非线程结束任务后的60s内没有新任务,线程才会被销毁,由于核心线程数为0,所有的线程均会被回收;同时线程数总数位无穷大,可以同时有非常多的线程。


4、newFixedThreadPool()源码分析


public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

     

核心线程数和最大线程数一样,都是传入的数值,销毁时间为0。


说明传入的线程都作为核心线程使用,并且使用之后立即销毁。


目录
打赏
0
0
0
0
178
分享
相关文章
|
5月前
|
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
304 60
【Java并发】【线程池】带你从0-1入门线程池
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
104 0
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
227 2
面试必看:如何设计一个可以优雅停止的线程?
嘿,大家好!我是小米。今天分享一篇关于“如何停止一个正在运行的线程”的面试干货。通过一次Java面试经历,我明白了停止线程不仅仅是技术问题,更是设计问题。Thread.stop()已被弃用,推荐使用Thread.interrupt()、标志位或ExecutorService来优雅地停止线程,避免资源泄漏和数据不一致。希望这篇文章能帮助你更好地理解Java多线程机制,面试顺利! 我是小米,喜欢分享技术的29岁程序员。欢迎关注我的微信公众号“软件求生”,获取更多技术干货!
169 53
面试大神教你:如何巧妙回答线程优先级这个经典考题?
大家好,我是小米。本文通过故事讲解Java面试中常见的线程优先级问题。小明和小华的故事帮助理解线程优先级:高优先级线程更可能被调度执行,但并非越高越好。实际开发需权衡业务需求,合理设置优先级。掌握线程优先级不仅能写出高效代码,还能在面试中脱颖而出。最后,小张因深入分析成功拿下Offer。希望这篇文章能助你在面试中游刃有余!
84 4
面试大神教你:如何巧妙回答线程优先级这个经典考题?
|
4月前
|
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
239 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
284 14
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
107 13
面试中的难题:线程异步执行后如何共享数据?
本文通过一个面试故事,详细讲解了Java中线程内部开启异步操作后如何安全地共享数据。介绍了异步操作的基本概念及常见实现方式(如CompletableFuture、ExecutorService),并重点探讨了volatile关键字、CountDownLatch和CompletableFuture等工具在线程间数据共享中的应用,帮助读者理解线程安全和内存可见性问题。通过这些方法,可以有效解决多线程环境下的数据共享挑战,提升编程效率和代码健壮性。
180 6
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问