Java线程池使用与原理详解

线程池是什么?

我们可以利用java很容易创建一个新线程,同时操作系统创建一个线程也是一笔不小的开销。所以基于线程的复用,就提出了线程池的概念,我们使用线程池创建出若干个线程,执行完一个任务后,该线程会存在一段时间(用户可以设定空闲线程的存活时间,后面会介绍),等到新任务来的时候就直接复用这个空闲线程,这样就省去了创建、销毁线程损耗。当然空闲线程也会是一种资源的浪费(所有才有空闲线程存活时间的限制),但总比频繁的创建销毁线程好太多。
下面是我的测试代码

  /*
   * @TODO 线程池测试
   */
  @Test
  public void threadPool(){

    /*java提供的统计线程运行数,一开始设置其值为50000,每一个线程任务执行完
     * 调用CountDownLatch#coutDown()方法(其实就是自减1)
     * 当所有的线程都执行完其值就为0
    */
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    Executor pool = Executors.newFixedThreadPool(10);//开启线程池最多会创建10个线程
    for(int i=0;i<50000;i++){
      pool.execute(new Runnable() {
        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50个线程都执行完了,共用时:"+(end-start)+"ms");
  }

  /**
   *@TODO 手动创建线程测试
   */
  @Test
  public void thread(){
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    for(int i=0;i<50000;i++){
      Thread thread = new Thread(new Runnable() {

        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
      thread.start();
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50000个线程都执行完了,共用时:"+(end-start)+"ms");

  }

使用线程池5w线程运行完大约为400ms,不使用线程池运行大约为4350ms左右,其效率可见一斑(读者可以自行测试,不过由于电脑配置不一样,跑出来的数据会有差别,但使用线程池绝对是比创建线程要快的)。

java如何使用线程池?

上面的测试代码中已经使用了线程池,下面正式介绍一下。

java所有的线程池最顶层是一个Executor接口,其只有一个execute方法,用于执行所有的任务,java又提供了ExecutorService接口继承自Executor并且扩充了一下方法,在往下就是AbstractExecutorService这个抽象类,其实现了ExecutorService,最后就是ThreadPoolExecutor其继承自上面的抽象类,我们常使用的java线程池就是创建的这个类的实例。

而上面我们使用Executors是一个工具类,它就是一个语法糖,为我们把各种不同的业务的线程池参数进行封装,进行new操作。

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

上面就是Executors.newFixedThreadPool(10)的源码。

下面重点来了,说一说ThreadPoolExecutor构造方法各参数的意思。

 public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue,
               ThreadFactory threadFactory,
               RejectedExecutionHandler handler)

上面这个构造方法是最全的。

下面我们根据源码来解释部分参数意思,这样更有说服力。

下面是ThreadPoolExecutor#execute方法,就是我们上面接口调用的execute实际执行者。

 public void execute(Runnable command) {
    if (command == null)
      throw new NullPointerException();

    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
      if (addWorker(command, true))
        return;
      c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
      int recheck = ctl.get();
      if (! isRunning(recheck) && remove(command))
        reject(command);
      else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
    }
    else if (!addWorker(command, false))
      reject(command);
  }

ctl是一个AtomicInteger实例,是一个提供了原子语句的CAS操作的类,它用来记录线程池中当前运行的线程数量加上-2^29,workCountOf方法就取得其绝对值(可以去看源码如何实现),当其小于corePoolSize时,会调用addWorker方法(是用来创建一个新Workder,Workder会创建一个Thread,所以就是创建线程的方法),addWorkd创建线程过程中会跟corePoolSize或者maxnumPoolSize的值比较(当传入true会根corePoolSize比较,false会根据maxnumPoolSize比较,大于等于其值会创建失败)。可见如何当前运行中的线程数量小于corePoolSize就是创建并且也会创建成功(
只简单的讨论线程池Running状态下)。

如果当运行中线程数大于等于corePoolSize时,进入第二个if,isRunning是跟SHUTDOWN(其值=0)比较,之前说过c等于当前运行的线程数量加上-2^29,如果当前当前运行的线程数据达到2^29时其值就=0,isRunning返回false,else中在执行addWorkd也会返回false(addWorkd也对其进行了检验),所以这表示线程池最多能支持2^29个线程同时运行(足够用了)。

workQueue.offer(command)就是将runnable加入等待队列,加入等待队列后runWorker方法会从队列中获取任务执行的。如果当前队列采用的是有界队列(ArrayBlockingQueue)当队列满了offer就会返回false,这是就进入else if,看!这里传入了false,说明这里要跟maxnumPoolSize比较了,如果这里运行的线程数大于等于maxnumPoolSize,那么这个线程任务就要被线程池拒绝了,执行reject(command),拒绝方法中使用了我们ThreadPoolExecutor构造方法中的RejectedExecutionHandler(拒绝策略),后面再详细解释。

经过上面的结合源码的介绍,下面对们ThreadPoolExecutor的参数介绍就好理解了。

线程池中线程创建和拒绝策略

corePoolSize,maxnumPoolSize,BlockingQueue这三个要一块说

当线程池运行的线程小于corePoolSize时,来一个新线程任务总是会新建一个线程来执行;当大于corePoolSize就会把任务加入到等待队列blockingQueue中,如果你传入的BlockingQueue是一个无界队列(LinkedBlockingQueue)这是队列可以存放“无穷多”的任务,所有总是会加入队列成功,跟maxnumPoolSize就没关系了,这也表示线程池中线程数最多为corePoolSize个;但是如果你传入的是有界队列(ArrayBlockingQueue,SynchronousQueue),当队列满时,并且线程数小于maxmunPoolSize就是创建新的线程直至线程数大于maxnumPoolSize;如果当线程数量大于maxnumPoolSize时,在加入任务就会被线程池拒绝。

RejectedExecutionHandler拒绝策略java给实现了4个AbortPolicy,CallerRunsPolicy,DiscardOldestPolicy,DiscardPolicy用户也可以自己实现该接口实现自己的拒绝策略;第一个就是直接抛出异常,我们可以进行trycatch处理;第二个就是该新任务直接运行;第三个是取消队列中最老的;第四个是取消当前任务。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

时间: 2017-10-07

Java代码构建一个线程池

在现代的操作系统中,有一个很重要的概念――线程,几乎所有目前流行的操作系统都支持线程,线程来源于操作系统中进程的概念,进程有自己的虚拟地址空间以及正文段.数据段及堆栈,而且各自占有不同的系统资源(例如文件.环境变量等等).与此不同,线程不能单独存在,它依附于进程,只能由进程派生.如果一个进程派生出了两个线程,那这两个线程共享此进程的全局变量和代码段,但每个线程各拥有各自的堆栈,因此它们拥有各自的局部变量,线程在UNIX系统中还被进一步分为用户级线程(由进程自已来管理)和系统级线程(由操作系统的调

Java Socket编程实例(三)- TCP服务端线程池

一.服务端回传服务类: import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.logging.Level; import java.util.logging.Logger; public class EchoProtocol implements Runnable { private static f

详谈Java几种线程池类型介绍及使用方法

一.线程池使用场景 •单个任务处理时间短 •将需处理的任务数量大 二.使用Java线程池好处 1.使用new Thread()创建线程的弊端: •每次通过new Thread()创建对象性能不佳. •线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom. •缺乏更多功能,如定时执行.定期执行.线程中断. 2.使用Java线程池的好处: •重用存在的线程,减少对象创建.消亡的开销,提升性能. •可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞

深入java线程池的使用详解

在Java 5.0之前启动一个任务是通过调用Thread类的start()方法来实现的,任务的提于交和执行是同时进行的,如果你想对任务的执行进行调度或是控制 同时执行的线程数量就需要额外编写代码来完成.5.0里提供了一个新的任务执行架构使你可以轻松地调度和控制任务的执行,并且可以建立一个类似数据库连接 池的线程池来执行任务.这个架构主要有三个接口和其相应的具体类组成.这三个接口是Executor, ExecutorService.ScheduledExecutorService,让我们先用一个图

Java线程池的几种实现方法和区别介绍

Java线程池的几种实现方法和区别介绍 import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.E

四种Java线程池用法解析

本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } } ).start(); 那你就out太多了,new Thread的弊端如下: a. 每次new Thread新建对象性能差. b. 线程缺乏统一管理,可能无限

支持生产阻塞的Java线程池

通常来说,生产任务的速度要大于消费的速度.一个细节问题是,队列长度,以及如何匹配生产和消费的速度. 一个典型的生产者-消费者模型如下:   在并发环境下利用J.U.C提供的Queue实现可以很方便地保证生产和消费过程中的线程安全.这里需要注意的是,Queue必须设置初始容量,防止生产者生产过快导致队列长度暴涨,最终触发OutOfMemory. 对于一般的生产快于消费的情况.当队列已满时,我们并不希望有任何任务被忽略或得不到执行,此时生产者可以等待片刻再提交任务,更好的做法是,把生产者阻塞在提交任

java中通用的线程池实例代码

复制代码 代码如下: package com.smart.frame.task.autoTask; import java.util.Collection;import java.util.Vector; /** * 任务分发器 */public class TaskManage extends Thread{    protected Vector<Runnable> tasks = new Vector<Runnable>();    protected boolean run

Java 线程池详解及实例代码

线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收. 所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁.如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因. 例如Android中常见到的很多通用组件一般都离不开"池"的概念,如各种图片

Java 线程池ExecutorService详解及实例代码

Java 线程池ExecutorService 1.线程池 1.1什么情况下使用线程池 单个任务处理的时间比较短. 将需处理的任务的数量大. 1.2使用线程池的好处 减少在创建和销毁线程上所花的时间以及系统资源的开销. 如果不使用线程池,有可能造成系统创建大量线程而导致消耗系统内存以及"过度切换"; 2.ExecutorService和Executors 2.1简介 ExecutorService是一个接口,继承了Executor, public interface ExecutorS

java多线程编程技术详解和实例代码

 java多线程编程技术详解和实例代码 1.   Java和他的API都可以使用并发. 可以指定程序包含不同的执行线程,每个线程都具有自己的方法调用堆栈和程序计数器,使得线程在与其他线程并发地执行能够共享程序范围内的资源,比如共享内存,这种能力被称为多线程编程(multithreading),在核心的C和C++语言中并不具备这种能力,尽管他们影响了JAVA的设计. 2.   线程的生命周期 新线程的生命周期从"新生"状态开始.程序启动线程前,线程一直是"新生"状态:

Java中初始化块详解及实例代码

Java中初始化块详解 在Java中,有两种初始化块:静态初始化块和非静态初始化块. 静态初始化块:使用static定义,当类装载到系统时执行一次.若在静态初始化块中想初始化变量,那仅能初始化类变量,即static修饰的数据成员. 非静态初始化块:在每个对象生成时都会被执行一次,可以初始化类的实例变量. 非静态初始化块会在构造函数执行时,且在构造函数主体代码执行之前被运行. 括号里的是初始化块,这里面的代码在创建Java对象时执行,而且在构造器之前执行! 其实初始化块就是构造器的补充,初始化块是

Java Process类的详解及实例代码

Java Process类的详解 前言: 今天用了下Java.lang.Process类,只是初步的学习,并没有深入实践,因为感觉它的用途并不是很大,偶尔才可能用上,如果要经常使用它的人可以自行参考JDk文档. 对Process类的简要说明: Process类是一个抽象类,方法都是抽象的,它封装了一个进程,也就是一个可执行的程序  该类提供进程的输入.执行输出到进程.等待进程的完成和检查进程的退出状态及销毁进程的方法 ProcessBuilder.start()和Runtime.exec方法创建

Java 垃圾回收机制详解及实例代码

 Java 垃圾回收机制详解 乍一看,垃圾回收所做的事情应当恰如其名--查找并清除垃圾.事实上却恰恰相反.垃圾回收会跟踪所有仍在使用的对象,然后将剩余的对象标记为垃圾.牢记了这点之后,我们再来深入地了解下这个被称为"垃圾回收"的自动化内存回收在JVM中到底是如何实现的. 手动管理内存 在介绍现代版的垃圾回收之前,我们先来简单地回顾下需要手动地显式分配及释放内存的那些日子.如果你忘了去释放内存,那么这块内存就无法重用了.这块内存被占有了却没被使用.这种场景被称之为内存泄露. 下面是用C写

java DataInputStream和DataOutputStream详解及实例代码

java DataInputStream和DataOutputStream详解 操作基本数据类型的流 DataInputStream DataOutputStream import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public c

java 嵌套类的详解及实例代码

java 嵌套类 到现在为止,我们都是在Java文件中直接定义类.这样的类出现在包(package)的级别上.Java允许类的嵌套定义. 这里将讲解如何在一个类中嵌套定义另一个类. 内部类 Java允许我们在类的内部定义一个类.如果这个类是没有static修饰符,那么这样一个嵌套在内部的类称为内部类(inner class). 内部类被认为是外部对象的一个成员.在定义内部类时,我们同样有访问权限控制(public, private, protected). 在使用内部类时,我们要先创建外部对象.

Java 回调机制(CallBack) 详解及实例代码

 Java 回调机制 概要: 最近学习java,接触到了回调机制(CallBack).初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBack做了一个定义.当然了,我在理解了回调之后,再去看网上的各种讲解,确实没什么问题.但是,对于初学的我来说,缺了一个循序渐进的过程.此处,将我对回调机制的个人理解,按照由浅到深的顺序描述一下,如有不妥之处,望不吝赐教! 开始之前,先想象一个场景:幼稚园的小朋友刚刚学习了10以内的加法. 第1章. 故事的缘起 幼师

Java 调用天气Webservice详解及实例代码

Java调用天气Webservice的小应用 废话不多说,直接贴代码: CityReq.java package com.weather; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name="getWeatherbyCityName",namespace="http://WebXml.com.cn/

Java Serializable和Parcelable详解及实例代码

对 Serializable和Parcelable理解 1.首先他们两个接口都是为了实现对象的序列化,使之可以传递,所谓序列化就是将对象信息装换成可以存储的介质的过程. 2.Serializable是jdk所提供的序列化接口,该接口存在于io包下,可想用于输入输出,使用非常简单,只要让你的类实现此接口就ok了:可以使用transient关键字修饰你不想序列化的属性. 3.Parcelable是sdk所提供的序列化接口,使用较上者麻烦,实现此接口后,需要重写writeToParcel方法,将需要序