Java多线程和线程池

Java多线程和线程池

1.为什么要使用线程池

在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,服务器应用程序需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利
用已有对象来进行服务,这就是“池化资源”技术产生的原因。

线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

2.线程池的组成部分

一个比较简单的线程池至少应包含线程池管理器、工作线程、任务列队、任务接口等部分。其中线程池管理器的作用是创建、销毁并管理线程池,将工作线程放入线程池中;工作线程是一个可以循环执行任务的线程,在没有任务是进行等待;任务列队的作用是提供一种缓冲机制,将没有处理的任务放在任务列队中;任务接口是每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执行状态等,工作线程通过该接口调度任务的执行。

线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务。

工作线程是一个可以循环执行任务的线程,在没有任务时将等待。

任务接口是为所有任务提供统一的接口,以便工作线程处理。任务接口主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。

3.线程池适合应用的场合

当一个服务器接受到大量短小线程的请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。但是线程要求的运动时间比较长,即线程的运行时间比…….

以上信息来自如下文章:http://www.blogjava.net/stevenjohn/archive/2011/12/12/366161.html

一、Java自带线程池

先看看Java自带线程池的例子,开启5个线程打印字符串List:

package com.luo.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadTest {
   

    public static void main(String[] args) {

        List<String> strList = new ArrayList<String>();
        for (int i = 0; i < 100; i++) {
            strList.add("String" + i);
        }
        int threadNum = strList.size() < 5 ? strList.size() : 5;
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, threadNum, 300,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(3),
                new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < threadNum; i++) {
            executor.execute(new PrintStringThread(i,strList,threadNum));
        }
        executor.shutdown();
    }
}

class PrintStringThread implements Runnable {

    private int num;

    private List<String> strList;

    private int threadNum;

    public PrintStringThread(int num, List<String> strList, int threadNum) {
        this.num = num;
        this.strList = strList;
        this.threadNum = threadNum;
    }

    public void run() {
        int length = 0;
        for(String str : strList){
            if (length % threadNum == num) {
                System.out.println("线程编号:" + num + ",字符串:" + str);
            }
            length ++;
        }
    }
}

Java自带线程池构造方法

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

corePoolSize: 线程池维护线程的最少线程数,也是核心线程数,包括空闲线程
maximumPoolSize: 线程池维护线程的最大线程数
keepAliveTime: 线程池维护线程所允许的空闲时间
unit: 程池维护线程所允许的空闲时间的单位
workQueue: 线程池所使用的缓冲队列
handler: 线程池对拒绝任务的处理策略

当一个任务通过execute(Runnable)方法欲添加到线程池时:
1、 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2、 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
3、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
4、 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
5、 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

分割线

事实上上面的例子代码写得有不足之处,如果你看出不足之处,说明你理解了线程池。否则可以多看几遍哦。

二、Spring线程池配置

3.1、直接调用

ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();  
//线程池所使用的缓冲队列 
poolTaskExecutor.setQueueCapacity(200);  
//线程池维护线程的最少数量 
poolTaskExecutor.setCorePoolSize(5);  
//线程池维护线程的最大数量 
poolTaskExecutor.setMaxPoolSize(1000);  
//线程池维护线程所允许的空闲时间 
poolTaskExecutor.setKeepAliveSeconds(30000);  
poolTaskExecutor.initialize(); 

3.2、通过配置文件

<bean id="poolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
   <!-- 核心线程数,默认为1 -->
   <property name="corePoolSize" value="5" />
   <!-- 最大线程数,默认为Integer.MAX_VALUE -->
   <property name="maxPoolSize" value="50" />
   <!-- 队列最大长度,一般需要设置值>=notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE -->
   <property name="queueCapacity" value="2000" />
   <!-- 线程池维护线程所允许的空闲时间,默认为60s -->
   <property name="keepAliveSeconds" value="100" />
   <!-- 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者 -->
   <property name="rejectedExecutionHandler">
       <!-- AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常 -->
       <!-- CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度 -->
       <!-- DiscardOldestPolicy:抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->
       <!-- DiscardPolicy:抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->
       <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
   </property>
</bean>
corePoolSize
核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。
核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。
maxPoolSize
当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。
keepAliveTime
当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
allowCoreThreadTimeout
是否允许核心线程空闲退出,默认值为false。
queueCapacity
任务队列容量。从maxPoolSize的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。

线程池按以下行为执行任务
当线程数小于核心线程数时,创建线程。
当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
当线程数大于等于核心线程数,且任务队列已满
若线程数小于最大线程数,创建线程
若线程数等于最大线程数,抛出异常,拒绝任务

系统负载
参数的设置跟系统的负载有直接的关系,下面为系统负载的相关参数:
tasks,每秒需要处理的最大任务数量
tasktime,处理第个任务所需要的时间
responsetime,系统允许任务最大的响应时间,比如每个任务的响应时间不得超过2秒。

参数设置

corePoolSize:
每个任务需要tasktime秒处理,则每个线程每钞可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即tasks*tasktime个线程数。假设系统每秒任务数为100~1000,每个任务耗时0.1秒,则需要100*0.11000*0.1,即10~100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下第秒任务数小于200,最多时为1000,则corePoolSize可设置为20queueCapacity:
任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)*responsetime: (20/0.1)*2=400,即队列长度可设置为400。
队列长度设置过大,会导致任务响应时间过长,切忌以下写法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。

maxPoolSize:
当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)*(20/200),即60个线程,可将maxPoolSize设置为60keepAliveTime:
线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。

allowCoreThreadTimeout:
默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。

以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能力,应设法降低每个任务的处理时间(tasktime)。

from:http://blog.csdn.net/zhouhl_cn/article/details/7392607

1、线程池的线程数设置需结合业务量、程序处理中IO和CPU使用占比、服务器CPU个数  设定最大线程数, 无特殊需要最大不能超过50个。
2、线程队列大小确保短时间业务量暴涨不会队列溢出或入队等待。
3、线程池建议采用可伸缩的线程池(core + max),支持超时idle释放 .

关闭线程池的时候需要优雅关闭:http://blog.csdn.net/luoxinwu123/article/details/7660625
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/2271.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • HashMap的扩容机制—resize()「建议收藏」

    HashMap的扩容机制—resize()「建议收藏」 HashMap的扩容机制—resize()什么时候扩容:当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值(知道这个阈字怎么念吗?不念fa值,念yu值四声)—即当前数组的长度乘以加载因子的值的时候,就要自动扩容啦。扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的…

  • 元 变化

    元 变化

    2021年12月17日
  • 吞吐量与并发的公式,优化和参考值的关系_并发量怎么计算

    吞吐量与并发的公式,优化和参考值的关系_并发量怎么计算下面的都是整理别人的加上自己的一些思考,有什么不对请多多指教。1.公式:响应时间(RT)是指系统对请求作出响应的时间。吞吐量(Throughput)是指系统在单位时间内处理请求的数量。并发用户数(Maximumconcurrentuser)是指系统可以同时承载的正常使用系统功能的用户的数量。吞吐量一般指相当一段时间内测量出来的系统单位时间处理的任务数或事务数(我的理解,…

  • vue 上传插件_vue上传文件前端完整实例

    vue 上传插件_vue上传文件前端完整实例插件描述:vue文件上传插件,可配置更新时间:2020-12-2310:17:131、本插件基于vue+element,使用前请先使用npminstall安装相关依赖2、运行项目npmrunserve3、打包项目npmrunbuild4、dist文件夹内为打包后的文件5、src内components组件为组件的源码6、因为是本地项目,因此不支持预览,但可在本插件基础上进行修改7、e…

  • linux下安装tomcat8

    linux下安装tomcat8(一)安装JDK环境64位JDK包:http://download.oracle.com/otn-pub/java/jdk/8u45-b14/jdk-8u45-linux-x64.tar.gz1.用 SecureCRT链接上linux,用命令直接下载 wget http://download.oracle.com/otn-pub/java/jdk/8u45-b14/jdk-8u45

  • 奶妈教程之基于ADS的带通滤波器仿真「建议收藏」

    奶妈教程之基于ADS的带通滤波器仿真「建议收藏」基于ADS的带通滤波器教学1.试验参数 版本:ADS2021 带通频率范围:3.3GHz~3.8GHz 带阻频率范围:3.1GHz~4.0GHz 特征阻抗:Z=50Ohm2.试验步骤(1)打开ADS软件并新建工程,如图所示(2)建立原理图工程点击图中红圈所示的图标,建立原理图建立带通滤波器工程然后会进入这样一个界面点击DesignGuide下的Filter点击弹出界面下红圈所示的指令此时整个程序界面如图所示,继续点击红圈所示的符号放入设计界面中然后选择图中红圈窗

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号