• 近期将进行后台系统升级,如有访问不畅,请稍后再试!
  • 极客文库-知识库上线!
  • 极客文库小编@勤劳的小蚂蚁,为您推荐每日资讯,欢迎关注!
  • 每日更新优质编程文章!
  • 更多功能模块开发中。。。

Java多线程和线程池


1.为什么要使用线程池
在 java 中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。

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

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

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

线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务。
工作线程是一个可以循环执行任务的线程,在没有任务时将等待。

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

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

一、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)方法欲添加到线程池时:
  • 如果此时线程池中的数量小于 corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue 未满,那么任务被放入缓冲队列。
  • 如果此时线程池中的数量大于 corePoolSize,缓冲队列 workQueue 满,并且线程池中的数量小于 maximumPoolSize,建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量大于 corePoolSize,缓冲队列 workQueue 满,并且线程池中的数量等于 maximumPoolSize,那么通过 handler 所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程 corePoolSize、任务队列 workQueue、最大线程 maximumPoolSize,如果三者都满了,使用 handler 处理被拒绝的任务。
  • 当线程池中的线程数量大于 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 的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。

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

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

参数设置

corePoolSize:
每个任务需要 tasktime 秒处理,则每个线程每钞可处理 1/tasktime 个任务。系统每秒有 tasks 个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即 tasks*tasktime 个线程数。假设系统每秒任务数为 100~1000,每个任务耗时 0.1 秒,则需要 100*0.1 至 1000*0.1,即 10~100 个线程。那么 corePoolSize 应该设置为大于 10,具体数字最好根据 8020 原则,即 80%情况下系统每秒任务数,若系统 80%的情况下第秒任务数小于 200,最多时为 1000,则 corePoolSize 可设置为 20。

queueCapacity:
任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(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 设置为 60。

keepAliveTime:
线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到 keepAliveTiime,该线程就退出。默认情况下线程池最少会保持 corePoolSize 个线程。

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


丨极客文库, 版权所有丨如未注明 , 均为原创丨
本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
转载请注明原文链接:Java 多线程和线程池
喜欢 (0)
[247507792@qq.com]
分享 (0)
勤劳的小蚂蚁
关于作者:
温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

欢迎 注册账号 登录 发表评论!

  • 精品技术教程
  • 编程资源分享
  • 问答交流社区
  • 极客文库知识库

客服QQ


QQ:2248886839


工作时间:09:00-23:00