热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

jdk自带线程池实例详解

在最近做的一个项目中,需要大量的使用到多线程和线程池,下面就java自带的线程池和大家一起分享

二、简介

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力,但频繁的创建线程的开销是很大的,那么如何来减少这部分的开销了,那么就要考虑使用线程池了。线程池就是一个线程的容器,每次只执行额定数量的线程,线程池就是用来管理这些额定数量的线程。

三、涉及线程池的类结构图

其中供我们使用的,主要是ThreadPoolExecutor类。

四、如何创建线程池

我们创建线程池一般有以下几种方法:

1、使用Executors工厂类

Executors主要提供了下面几种创建线程池的方法:

下面来看下使用示例:

1)newFixedThreadPool(固定大小的线程池)

public class FixedThreadPool { 
  public static void main(String[] args) { 
    ExecutorService pool = Executors.newFixedThreadPool(5);// 创建一个固定大小为5的线程池 
    for (int i = 0; i <10; i++) { 
      pool.submit(new MyThread()); 
    } 
    pool.shutdown(); 
  } 
} 
public class MyThread extends Thread { 
  @Override 
  public void run() { 
    System.out.println(Thread.currentThread().getName() + "正在执行。。。"); 
  } 
} 

测试结果如下:

pool-1-thread-1正在执行。。。 
pool-1-thread-2正在执行。。。 
pool-1-thread-3正在执行。。。 
pool-1-thread-2正在执行。。。 
pool-1-thread-3正在执行。。。 
pool-1-thread-2正在执行。。。 
pool-1-thread-2正在执行。。。 
pool-1-thread-3正在执行。。。 
pool-1-thread-5正在执行。。。 
pool-1-thread-4正在执行。。。 

固定大小的线程池:每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程线。

2)newSingleThreadExecutor(单线程的线程池)

public class SingleThreadPool { 
  public static void main(String[] args) {  
    ExecutorService pool=Executors.newSingleThreadExecutor();//创建一个单线程池  
    for(int i=0;i<100;i++){  
      pool.submit(new MyThread());  
    }  
    pool.shutdown();  
  } 
} 

测试结果如下:

pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 

单线程的线程池:这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

3)newScheduledThreadPool

public class ScheduledThreadPool { 
  public static void main(String[] args) {  
    ScheduledExecutorService pool=Executors.newScheduledThreadPool(6);  
    for(int i=0;i<10000;i++){  
      pool.submit(new MyThread());  
    }  
      
    pool.schedule(new MyThread(), 1000, TimeUnit.MILLISECONDS);  
    pool.schedule(new MyThread(), 1000, TimeUnit.MILLISECONDS);  
    pool.shutdown();  
  }  
} 

测试结果如下:

pool-1-thread-1正在执行。。。 
pool-1-thread-6正在执行。。。 
pool-1-thread-5正在执行。。。 
pool-1-thread-4正在执行。。。 
pool-1-thread-2正在执行。。。 
pool-1-thread-3正在执行。。。 
pool-1-thread-4正在执行。。。 
pool-1-thread-5正在执行。。。 
pool-1-thread-6正在执行。。。 
pool-1-thread-1正在执行。。。 
…………此处会延时1S………… 
pool-1-thread-4正在执行。。。 
pool-1-thread-1正在执行。。。 

测试结果的最后两个线程都是在延时1S之后,才开始执行的。此线程池支持定时以及周期性执行任务的需求

4)newCachedThreadPool(可缓存的线程池)

public class CachedThreadPool { 
  public static void main(String[] args) {  
    ExecutorService pool=Executors.newCachedThreadPool();  
    for(int i=0;i<100;i++){  
      pool.submit(new MyThread());  
    }  
    pool.shutdown();  
  }  
} 

测试结果如下:

pool-1-thread-5正在执行。。。 
pool-1-thread-7正在执行。。。 
pool-1-thread-5正在执行。。。 
pool-1-thread-16正在执行。。。 
pool-1-thread-17正在执行。。。 
pool-1-thread-16正在执行。。。 
pool-1-thread-5正在执行。。。 
pool-1-thread-7正在执行。。。 
pool-1-thread-16正在执行。。。 
pool-1-thread-18正在执行。。。 
pool-1-thread-10正在执行。。。 

可缓存的线程池:如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

官方建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程),这几种线程池均为大多数使用场景预定义了默认配置。

2、继承ThreadPoolExecutor类,并复写父类的构造方法。

在介绍这种方式之前,我们来分析下前面几个创建线程池的底层代码是怎样的?

public class Executors { 
  public static ExecutorService newFixedThreadPool(int nThreads) { 
    return new ThreadPoolExecutor(nThreads, nThreads, 
                   0L, TimeUnit.MILLISECONDS, 
                   new LinkedBlockingQueue()); 
} 
  public static ExecutorService newSingleThreadExecutor() { 
    return new FinalizableDelegatedExecutorService 
      (new ThreadPoolExecutor(1, 1, 
                  0L, TimeUnit.MILLISECONDS, 
                  new LinkedBlockingQueue())); 
  } 
} 

从Executors工厂类的底层代码可以看出,工厂类提供的创建线程池的方法,其实都是通过构造ThreadPoolExecutor来实现的。ThreadPoolExecutor构造方法代码如下:

public ThreadPoolExecutor(int corePoolSize, 
               int maximumPoolSize, 
               long keepAliveTime, 
               TimeUnit unit, 
               BlockingQueue workQueue, 
               ThreadFactory threadFactory, 
               RejectedExecutionHandler handler) { 
    if (corePoolSize <0 || 
      maximumPoolSize <= 0 || 
      maximumPoolSize 

那么接下来,我们就来谈谈这个ThreadPoolExecutor构造方法。在这个构造方法中,主要有以下几个参数:

corePoolSize--池中所保存的线程数,包括空闲线程。

maximumPoolSize--池中允许的最大线程数。

keepAliveTime--当线程数大于corePoolSize时,此为终止空闲线程等待新任务的最长时间。

Unit--keepAliveTime 参数的时间单位。

workQueue--执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。

threadFactory--执行程序创建新线程时使用的工厂。

Handler--由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

接下来,咋们来说下这几个参数之间的关系。当线程池刚创建的时候,线程池里面是没有任何线程的(注意,并不是线程池一创建,里面就创建了一定数量的线程),当调用execute()方法添加一个任务时,线程池会做如下的判断:

1)如果当前正在运行的线程数量小于corePoolSize,那么立刻创建一个新的线程,执行这个任务。

2)如果当前正在运行的线程数量大于或等于corePoolSize,那么这个任务将会放入队列中。

3)如果线程池的队列已经满了,但是正在运行的线程数量小于maximumPoolSize,那么还是会创建新的线程,执行这个任务。

4)如果队列已经满了,且当前正在运行的线程数量大于或等于maximumPoolSize,那么线程池会根据拒绝执行策略来处理当前的任务。

5)当一个任务执行完后,线程会从队列中取下一个任务来执行,如果队列中没有需要执行的任务,那么这个线程就会处于空闲状态,如果超过了keepAliveTime存活时间,则这个线程会被线程池回收(注:回收线程是有条件的,如果当前运行的线程数量大于corePoolSize的话,这个线程就会被销毁,如果不大于corePoolSize,是不会销毁这个线程的,线程的数量必须保持在corePoolSize数量内).为什么不是线程一空闲就回收,而是需要等到超过keepAliveTime才进行线程的回收了,原因很简单:因为线程的创建和销毁消耗很大,更不能频繁的进行创建和销毁,当超过keepAliveTime后,发现确实用不到这个线程了,才会进行销毁。这其中unit表示keepAliveTime的时间单位,unit的定义如下:

public enum TimeUnit { 
  NANOSECONDS { 
    // keepAliveTime以纳秒为单位 
  }, 
  MICROSECONDS { 
    // keepAliveTime以微秒为单位 
  }, 
  MILLISECONDS { 
    // keepAliveTime以毫秒为单位 
  }, 
  SECONDS { 
    // keepAliveTime以秒为单位 
  }, 
  MINUTES { 
    // keepAliveTime以分钟为单位 
  }, 
  HOURS { 
    // keepAliveTime以小时为单位 
  }, 
  DAYS { 
    // keepAliveTime以天为单位 
  }; 

下面从源码来分析一下,对于上面的几种情况,主要涉及到的源码有以下几块:

private boolean addIfUnderCorePoolSize(Runnable firstTask) { 
    Thread t = null; 
    final ReentrantLock mainLock = this.mainLock; 
    mainLock.lock(); 
    try { 
      if (poolSize 

其实,这段代码很简单,主要描述的就是,如果当前的线程池小于corePoolSize的时候,是直接新建一个线程来处理任务。 

private boolean addIfUnderMaximumPoolSize(Runnable firstTask) { 
    Thread t = null; 
    final ReentrantLock mainLock = this.mainLock; 
    mainLock.lock(); 
    try { 
      if (poolSize 

上面这段代码描述的是,如果当前线程池的数量小于maximumPoolSize的时候,也会创建一个线程,来执行任务 

五、线程池的队列

线程池的队列,总的来说有3种:

直接提交:工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

无界队列:使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

有界队列:当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

下面就来说下线程池的队列,类结构图如下:

1)SynchronousQueue

该队列对应的就是上面所说的直接提交,首先SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。

2)LinkedBlockingQueue

该队列对应的就是上面的无界队列。

3)ArrayBlockingQueue

该队列对应的就是上面的有界队列。ArrayBlockingQueue有以下3中构造方法:

public ArrayBlockingQueue(int capacity) { 
    this(capacity, false); 
  } 
  public ArrayBlockingQueue(int capacity, boolean fair) { 
    if (capacity <= 0) 
      throw new IllegalArgumentException(); 
    this.items = (E[]) new Object[capacity]; 
    lock = new ReentrantLock(fair); 
    notEmpty = lock.newCondition(); 
    notFull = lock.newCondition(); 
} 
  public ArrayBlockingQueue(int capacity, boolean fair, 
               Collection<&#63; extends E> c) { 
    this(capacity, fair); 
    if (capacity  it = c.iterator(); it.hasNext();) 
      add(it.next()); 
  } 

下面我们重点来说下这个fair,fair表示队列访问线程的竞争策略,当为true的时候,任务插入队列遵从FIFO的规则,如果为false,则可以“插队”。举个例子,假如现在有很多任务在排队,这个时候正好一个线程执行完了任务,同时又新来了一个任务,如果为false的话,这个任务不用在队列中排队,可以直接插队,然后执行。如下图所示:

六、线程池的拒绝执行策略

当线程的数量达到最大值时,这个时候,任务还在不断的来,这个时候,就只好拒绝接受任务了。

ThreadPoolExecutor 允许自定义当添加任务失败后的执行策略。你可以调用线程池的 setRejectedExecutionHandler() 方法,用自定义的RejectedExecutionHandler 对象替换现有的策略,ThreadPoolExecutor提供的默认的处理策略是直接丢弃,同时抛异常信息,ThreadPoolExecutor 提供 4 个现有的策略,分别是:
ThreadPoolExecutor.AbortPolicy:表示拒绝任务并抛出异常,源码如下:

public static class AbortPolicy implements RejectedExecutionHandler { 
    /** 
     * Creates an AbortPolicy. 
     */ 
    public AbortPolicy() { } 
    /** 
     * Always throws RejectedExecutionException. 
     * @param r the runnable task requested to be executed 
     * @param e the executor attempting to execute this task 
     * @throws RejectedExecutionException always. 
     */ 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 
      throw new RejectedExecutionException(); //抛异常 
    } 
  } 

 ThreadPoolExecutor.DiscardPolicy:表示拒绝任务但不做任何动作,源码如下:

public static class DiscardPolicy implements RejectedExecutionHandler { 
    /** 
     * Creates a DiscardPolicy. 
     */ 
    public DiscardPolicy() { } 
    /** 
     * Does nothing, which has the effect of discarding task r. 
     * @param r the runnable task requested to be executed 
     * @param e the executor attempting to execute this task 
     */ 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 
    } // 直接拒绝,但不做任何操作 
  } 

ThreadPoolExecutor.CallerRunsPolicy:表示拒绝任务,并在调用者的线程中直接执行该任务,源码如下:

public static class CallerRunsPolicy implements RejectedExecutionHandler { 
    /** 
     * Creates a CallerRunsPolicy. 
     */ 
    public CallerRunsPolicy() { } 
    /** 
     * Executes task r in the caller's thread, unless the executor 
     * has been shut down, in which case the task is discarded. 
     * @param r the runnable task requested to be executed 
     * @param e the executor attempting to execute this task 
     */ 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 
      if (!e.isShutdown()) { 
        r.run(); // 直接执行任务 
      } 
    } 
  } 

 ThreadPoolExecutor.DiscardOldestPolicy:表示先丢弃任务队列中的第一个任务,然后把这个任务加进队列。源码如下:

public static class DiscardOldestPolicy implements RejectedExecutionHandler { 
    /** 
     * Creates a DiscardOldestPolicy for the given executor. 
     */ 
    public DiscardOldestPolicy() { } 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 
      if (!e.isShutdown()) { 
        e.getQueue().poll(); // 丢弃队列中的第一个任务 
        e.execute(r); // 执行新任务 
      } 
    } 
} 

当任务源源不断到来的时候,会从Queue中poll一个任务出来,然后执行新的任务 

总结

以上所述是小编给大家介绍的jdk自带线程池详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


推荐阅读
  • 本文详细介绍了Python的multiprocessing模块,该模块不仅支持本地并发操作,还支持远程操作。通过使用multiprocessing模块,开发者可以利用多核处理器的优势,提高程序的执行效率。 ... [详细]
  • 本文深入探讨了分布式文件系统的核心概念及其在现代数据存储解决方案中的应用,特别是针对大规模数据处理的需求。文章不仅介绍了多种流行的分布式文件系统和NoSQL数据库,还提供了选择合适系统的指导原则。 ... [详细]
  • Golang与微服务架构:构建高效微服务
    本文探讨了Golang在微服务架构中的应用,包括Golang的基本概念、微服务开发的优势、常用开发工具以及具体实践案例。 ... [详细]
  • 本文介绍了如何使用Workman框架构建一个功能全面的即时通讯系统,该系统不仅支持一对一聊天、群组聊天,还集成了视频会议和实时音视频通话功能,同时提供了红包发送等附加功能。 ... [详细]
  • 解决宝塔面板Nginx反向代理缓存问题
    本文介绍如何在宝塔控制面板中通过编辑Nginx配置文件来解决反向代理中的缓存问题,确保每次请求都能从服务器获取最新的数据。 ... [详细]
  • 工作中频繁在不同Linux服务器之间切换时,频繁输入密码不仅耗时还影响效率。本文介绍如何通过设置SSH密钥认证,简化登录流程,提高工作效率。 ... [详细]
  • 在日常运维中,频繁地对多台Linux服务器进行用户管理是一项耗时的任务。为了提高效率,可以通过编写Expect脚本来实现远程自动化操作,从而简化这一过程。 ... [详细]
  • Redis: 高效的键值存储系统
    Redis是一款遵循BSD许可的开源高性能键值存储系统,它不仅支持多种数据类型的存储,还提供了数据持久化和复制等功能,显著区别于其他键值缓存解决方案。 ... [详细]
  • Hadoop集群搭建:实现SSH无密码登录
    本文介绍了如何在CentOS 7 64位操作系统环境下配置Hadoop集群中的SSH无密码登录,包括环境准备、用户创建、密钥生成及配置等步骤。 ... [详细]
  • 本文探讨了在不同场景下如何高效且安全地存储Token,包括使用定时器刷新、数据库存储等方法,并针对个人开发者与第三方服务平台的不同需求提供了具体建议。 ... [详细]
  • 本文探讨了一个Web工程项目的需求,即允许用户随时添加定时任务,并通过Quartz框架实现这些任务的自动化调度。文章将介绍如何设计任务表以存储任务信息和执行周期,以及如何通过一个定期扫描机制自动识别并加载新任务到调度系统中。 ... [详细]
  • 本文详细介绍了如何在Windows和Linux系统上配置Openfire服务器,包括安装步骤、数据库配置及端口映射等关键环节。 ... [详细]
  • Maven快照版本管理及更新策略详解
    本文深入探讨了Maven中的快照版本管理和更新策略,解释了快照版本与正式版本的区别,并提供了如何配置快照更新策略的方法,以确保项目依赖始终保持最新。 ... [详细]
  • 实现Win10与Linux服务器的SSH无密码登录
    本文介绍了如何在Windows 10环境下使用Git工具,通过配置SSH密钥对,实现与Linux服务器的无密码登录。主要步骤包括生成本地公钥、上传至服务器以及配置服务器端的信任关系。 ... [详细]
  • PHP中Smarty模板引擎自定义函数详解
    本文详细介绍了如何在PHP的Smarty模板引擎中自定义函数,并通过具体示例演示了这些函数的使用方法和应用场景。适合PHP后端开发者学习。 ... [详细]
author-avatar
濛宝贝儿
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有