JAVA线程池原理实例详解

本文实例讲述了JAVA线程池原理。分享给大家供大家参考,具体如下:

线程池的优点

1、线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。

2、可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。

线程池的创建

public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue,
               RejectedExecutionHandler handler) 
  • corePoolSize:线程池核心线程数量
  • maximumPoolSize:线程池最大线程数量
  • keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
  • unit:存活时间的单位
  • workQueue:存放任务的队列
  • handler:超出线程范围和队列容量的任务的处理程序

线程池的实现原理

提交一个任务到线程池中,线程池的处理流程如下:

1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。

2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

线程池的源码解读

1、ThreadPoolExecutor的execute()方法

public void execute(Runnable command) {
    if (command == null)
      throw new NullPointerException(); //如果线程数大于等于基本线程数或者线程创建失败,将任务加入队列
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {//线程池处于运行状态并且加入队列成功
      if (runState == RUNNING && workQueue.offer(command)) {
        if (runState != RUNNING || poolSize == 0)
          ensureQueuedTaskHandled(command);
      }//线程池不处于运行状态或者加入队列失败,则创建线程(创建的是非核心线程)
      else if (!addIfUnderMaximumPoolSize(command))//创建线程失败,则采取阻塞处理的方式
        reject(command); // is shutdown or saturated
    }
}

2、创建线程的方法:addIfUnderCorePoolSize(command)

private boolean addIfUnderCorePoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
      if (poolSize < corePoolSize && runState == RUNNING)
        t = addThread(firstTask);
    } finally {
      mainLock.unlock();
    }
    if (t == null)
      return false;
    t.start();
    return true;
}

我们重点来看第7行:

private Thread addThread(Runnable firstTask) {
    Worker w = new Worker(firstTask);
    Thread t = threadFactory.newThread(w);
    if (t != null) {
      w.thread = t;
      workers.add(w);
      int nt = ++poolSize;
      if (nt > largestPoolSize)
        largestPoolSize = nt;
    }
    return t;
}

这里将线程封装成工作线程worker,并放入工作线程组里,worker类的方法run方法:

public void run() {
  try {
    Runnable task = firstTask;
    firstTask = null;
    while (task != null || (task = getTask()) != null) {
      runTask(task);
      task = null;
    }
  } finally {
    workerDone(this);
  }
}

worker在执行完任务后,还会通过getTask方法循环获取工作队里里的任务来执行。

我们通过一个程序来观察线程池的工作原理:

1、创建一个线程

public class ThreadPoolTest implements Runnable
{
  @Override
  public void run()
  {
    try
    {
      Thread.sleep(300);
    }
    catch (InterruptedException e)
    {
      e.printStackTrace();
    }
  }
}

2、线程池循环运行16个线程:

public static void main(String[] args)
{
    LinkedBlockingQueue<Runnable> queue =
      new LinkedBlockingQueue<Runnable>(5);
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, queue);
    for (int i = 0; i < 16 ; i++)
    {
      threadPool.execute(
        new Thread(new ThreadPoolTest(), "Thread".concat(i + "")));
      System.out.println("线程池中活跃的线程数: " + threadPool.getPoolSize());
      if (queue.size() > 0)
      {
        System.out.println("----------------队列中阻塞的线程数" + queue.size());
      }
    }
    threadPool.shutdown();
}

执行结果:

线程池中活跃的线程数: 1
线程池中活跃的线程数: 2
线程池中活跃的线程数: 3
线程池中活跃的线程数: 4
线程池中活跃的线程数: 5
线程池中活跃的线程数: 5
----------------队列中阻塞的线程数1
线程池中活跃的线程数: 5
----------------队列中阻塞的线程数2
线程池中活跃的线程数: 5
----------------队列中阻塞的线程数3
线程池中活跃的线程数: 5
----------------队列中阻塞的线程数4
线程池中活跃的线程数: 5
----------------队列中阻塞的线程数5
线程池中活跃的线程数: 6
----------------队列中阻塞的线程数5
线程池中活跃的线程数: 7
----------------队列中阻塞的线程数5
线程池中活跃的线程数: 8
----------------队列中阻塞的线程数5
线程池中活跃的线程数: 9
----------------队列中阻塞的线程数5
线程池中活跃的线程数: 10
----------------队列中阻塞的线程数5
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[Thread15,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@232204a1[Running, pool size = 10, active threads = 10, queued tasks = 5, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    at test.ThreadTest.main(ThreadTest.java:17)

从结果可以观察出:

1、创建的线程池具体配置为:核心线程数量为5个;全部线程数量为10个;工作队列的长度为5。
2、我们通过queue.size()的方法来获取工作队列中的任务数。
3、运行原理:

刚开始都是在创建新的线程,达到核心线程数量5个后,新的任务进来后不再创建新的线程,而是将任务加入工作队列,任务队列到达上线5个后,新的任务又会创建新的普通线程,直到达到线程池最大的线程数量10个,后面的任务则根据配置的饱和策略来处理。我们这里没有具体配置,使用的是默认的配置AbortPolicy:直接抛出异常。
当然,为了达到我需要的效果,上述线程处理的任务都是利用休眠导致线程没有释放!!!

RejectedExecutionHandler:饱和策略

当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进行处理。这个策略默认配置是AbortPolicy,表示无法处理新的任务而抛出异常。JAVA提供了4中策略:

1、AbortPolicy:直接抛出异常
2、CallerRunsPolicy:只用调用所在的线程运行任务
3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
4、DiscardPolicy:不处理,丢弃掉。

我们现在用第四种策略来处理上面的程序:

public static void main(String[] args)
{
    LinkedBlockingQueue<Runnable> queue =
      new LinkedBlockingQueue<Runnable>(3);
    RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);
    for (int i = 0; i < 9 ; i++)
    {
      threadPool.execute(
        new Thread(new ThreadPoolTest(), "Thread".concat(i + "")));
      System.out.println("线程池中活跃的线程数: " + threadPool.getPoolSize());
      if (queue.size() > 0)
      {
        System.out.println("----------------队列中阻塞的线程数" + queue.size());
      }
    }
    threadPool.shutdown();
}

执行结果:

线程池中活跃的线程数: 1
线程池中活跃的线程数: 2
线程池中活跃的线程数: 2
----------------队列中阻塞的线程数1
线程池中活跃的线程数: 2
----------------队列中阻塞的线程数2
线程池中活跃的线程数: 2
----------------队列中阻塞的线程数3
线程池中活跃的线程数: 3
----------------队列中阻塞的线程数3
线程池中活跃的线程数: 4
----------------队列中阻塞的线程数3
线程池中活跃的线程数: 5
----------------队列中阻塞的线程数3
线程池中活跃的线程数: 5
----------------队列中阻塞的线程数3

这里采用了丢弃策略后,就没有再抛出异常,而是直接丢弃。在某些重要的场景下,可以采用记录日志或者存储到数据库中,而不应该直接丢弃。

设置策略有两种方式:

1、

RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);

2、

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue);
threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());

更多java相关内容感兴趣的读者可查看本站专题:《Java进程与线程操作技巧总结》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总》

希望本文所述对大家java程序设计有所帮助。

时间: 2019-03-30

详解java中的深拷贝和浅拷贝(clone()方法的重写、使用序列化实现真正的深拷贝)

1.序列化实现 public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T object){ T cloneObj = null; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new Objec

java request.getHeader("user-agent")获取浏览器信息的方法

一.User Agent的含义 User Agent中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本.CPU 类型.浏览器及版本.浏览器渲染引擎.浏览器语言.浏览器插件等. 一些网站常常通过判断 UA 来给不同的操作系统.不同的浏览器发送不同的页面,因此可能造成某些页面无法在某个浏览器中正常显示,但通过伪装 UA 可以绕过检测. 浏览器的 UA 字串 标准格式为: 浏览器标识 (操作系统标识; 加密等级标识; 浏览器语言) 渲染引擎标识 版本信息 浏

微信小程序实现获取小程序码和二维码java接口开发

前言:目前小程序推出了自己的识别码,小程序码,这个圆形的码看起来比二维码好看.本文总结微信小程序的获取小程序码和二维码并生成二维码图片的接口开发.主要内容摘抄自微信小程序的API文档,java接口开发是自己总结开发. 微信小程序API文档:获取二维码 一.简介 通过后台接口可以获取小程序任意页面的二维码,扫描该二维码可以直接进入小程序对应的页面.目前微信支持两种二维码,小程序码(左),小程序二维码(右),如下所示: 二.获取小程序码 目前有两个接口可以生成小程序码,开发者可以根据自己的需要选择合

作为程序员必须掌握的Java虚拟机中的22个重难点(推荐0

Java虚拟机一直是比较重要的知识点,是Java高级开发必会的.本文为你总结了关于JVM的22个重点.难点,图文并茂的向你展示和JVM有关的重点知识.全文共7000字左右. 概念 虚拟机:指以软件的方式模拟具有完整硬件系统功能.运行在一个完全隔离环境中的完整计算机系统 ,是物理机的软件实现.常用的虚拟机有VMWare,Visual Box,Java Virtual Machine(Java虚拟机,简称JVM). Java虚拟机阵营:Sun HotSpot VM.BEA JRockit VM.IB

利用weixin-java-miniapp生成小程序码并直接返回图片文件流的方法

有时候我们可能需要在其他的网页上展示我们自己的小程序中某些页面的小程序码,这种时候,我们需要用到小程序的生成小程序码的相关接口. 工具选型 我们仍然选用简单方便的weixin-java-miniapp来完成此功能. 项目配置 详见我们的另一篇文章点此进入 生成小程序码的相关类型 小程序码的其他生成方式以及相关类型在这篇文章点此进入中介绍的较为详细,此处不再赘述,以下仅以生成不限制张数的这种类型来做一个示例. 生成小程序码图片 先获取小程序的service实例wxMaService. 再获取二维码

Java对类私有变量的暴力反射技术讲解

Java对类私有变量的暴力反射 假设有一个类,他有一个私有变量: package com.howlaa.day04; public class ReflectPoint { private int priVar; public ReflectPoint(int priVar){ this.priVar =priVar; } } 如果我们直接采用.get的方式,是不可能看到私有变量的. 我们可以这样: package com.howlaa.day04; import java.lang.refle

详解Java发送HTTP请求

前言 请求http的Demo是个人亲测过,目前该方式已经在线上运行着.因为是http请求,所有发送post 和get 请求的demo都有在下方贴出,包括怎么测试,大家可直接 copy到自己的项目中使用. 正文 使用须知 为了避免大家引错包我把依赖和涉及到包路径给大家 import java.net.HttpURLConnection; import java.net.URI; import org.apache.http.HttpResponse; import org.apache.http.

Java 集合系列(二)ArrayList详解

ArrayList ArrayList 是通过一个数组来实现的,因此它是在连续的存储位置存放对象的引用,只不过它比 Array 更智能,能够根据集合长度进行自动扩容. 假设让我们来实现一个简单的能够自动扩容的数组,我们最容易想到的点就是: add()的时候需要判断当前数组size+1是否等于此时定义的数组大小: 若小于直接添加即可:否则,需要先扩容再进行添加. 实际上,ArrayList的内部实现原理也是这样子,我们可以来研究分析一下ArrayList的源码 add(E e) 源码分析 /**

Java在利用反射条件下替换英文字母中的值

Java在利用反射条件下替换英文字母中的值 (1)创建两个Class: ReflectTest类如下: package cn.itcast.day01; import java.lang.reflect.Constructor; import java.lang.reflect.Field; public class ReflectTest { public static void main(String[] args) throws Exception { changeStringValue(

拳皇(Java简单的小程序)代码实例

刚开始学习Java,看完老九君的视频根据他的内容敲的代码,感觉还挺有成就感的,毕竟刚学习Java. package helloasd;import java.util.*; public class hellojava { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.print("输入名称: "); //用户自己输入名字 String userna

Python简单基础小程序的实例代码

1 九九乘法表 for i in range(9):#从0循环到8 i += 1#等价于 i = i+1 for j in range(i):#从0循环到i j += 1 print(j,'*',i,'=',i*j,end = ' ',sep='') # end默认在结尾输出换行,将它改成空格 sep 默认 j,'*',i,'=',i*j 各元素输出中间会有空格 print()#这里作用是输出换行符 i = 1 while i <= 9: j = 1 while j <= i: print(&

python英语单词测试小程序代码实例

这篇文章主要介绍了python英语单词测试小程序代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 爬取了扇贝英语网,并制作了一个英语单词测试的小程序,还能生成错词本,一起来看下代码吧- import requests #扇贝网爬虫,获取英语单词 category_res=requests.get('https://www.shanbay.com/api/v1/vocabtest/category/?_=1566889802182') ca

python实现简单的购物程序代码实例

需求: 启动程序后,让用户输入工资,然后打印商品列表 允许用户根据商品编号购买商品 用户选择商品后,检测余额是否够,够就直接扣款,不够就提醒 可随时退出,退出时,打印已购买商品和余额 代码如下 #!/usr/bin/ven python # Author: Hawkeye ''' 本程序为实例程序:购物车程序 需求: 启动程序后,让用户输入工资,然后打印商品列表 允许用户根据商品编号购买商品 用户选择商品后,检测余额是否够,够就直接扣款,不够就提醒 可随时退出,退出时,打印已购买商品和余额 ''

Java防锁屏小程序代码实例

为防止系统桌面自动锁屏,只需打成jar包,写个批处理程序start.bat,双击执行保持dos窗口执行即可,无其他影响. 程序设计为每30秒动一次鼠标,可根据需要调整. 附代码: package main; import java.awt.AWTException; import java.awt.Dimension; import java.awt.MouseInfo; import java.awt.Point; import java.awt.PointerInfo; import jav

Java Applet查找素数小程序代码实例

1. Applet 这个远古的东西,今天我同学让我帮他看看代码,说applet运行出错.额,反正闲着也是闲着,看看呗 ,结果看到代码... 2.就是实现这破玩意 package calculate; import java.applet.Applet; import java.awt.*; import java.awt.event.*; public abstract class primeNumBetween extends Applet implements ActionListener

java 简单的计算器程序实例代码

java 简单的计算器程序 实现实例: import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Calculator { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { CalculatorFrame frame = new Calculato

java网络通信技术之简单聊天小程序

本文实例为大家分享了java实现简单聊天小程序的具体代码,供大家参考,具体内容如下 再学习完java的通信技术后,做了一个简单的窗体聊天程序.程序非常简单,主要目的是当练习巩固自己所学的东西,在这里写出来记录以下.下面直接上代码. 首先是服务端代码: package ChatTwoPackage; import java.io.*; import java.net.*; public class ChatTwoServer { public ChatTwoServer(int port,Stri

微信小程序 参数传递实例代码

微信小程序 参数传递实例代码 1.通过事件传递参数 实例代码: <view data-id="103" bindtap="evenName"></view> Page({ evenName: function(e) { //获得点击事件传递的id console.log(e.target.dataset.id); })  2.通过页面跳转传递参数 页面1: wx.navigateTo({ url: '/pages/scan-order/sca

android Socket实现简单聊天小程序

android Socket实现简单聊天小程序,供大家参考,具体内容如下 服务器端: package org.hwq.echo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public cla