线程池是什么?线程池的作用?

为了更好的理解,首先来看一篇JAVA中的线程池介绍:

一、简介
线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为:

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

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

一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。

当一个任务通过execute(Runnable)方法欲添加到线程池时:

如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

也就是:处理任务的优先级为:
核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:
NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。

workQueue我常用的是:java.util.concurrent.ArrayBlockingQueue

handler有四个选择:
ThreadPoolExecutor.AbortPolicy()
抛出java.util.concurrent.RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy()
重试添加当前的任务,他会自动重复调用execute()方法
ThreadPoolExecutor.DiscardOldestPolicy()
抛弃旧的任务
ThreadPoolExecutor.DiscardPolicy()
抛弃当前的任务


二、一般用法举例
//------------------------------------------------------------
//TestThreadPool.java
//package cn.simplelife.exercise;

import java.io.Serializable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThreadPool {

private static int produceTaskSleepTime = 2;
private static int consumeTaskSleepTime = 2000;
private static int produceTaskMaxNumber = 10;

public static void main(String[] args) {

//构造一个线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardOldestPolicy());

for(int i=1;i<=produceTaskMaxNumber;i++){
try {
//产生一个任务,并将其加入到线程池
String task = "task@ " + i;
System.out.println("put " + task);
threadPool.execute(new ThreadPoolTask(task));

//便于观察,等待一段时间
Thread.sleep(produceTaskSleepTime);
} catch (Exception e) {
e.printStackTrace();
}
}
}

/**
* 线程池执行的任务
* @author hdpan
*/
public static class ThreadPoolTask implements Runnable,Serializable{
private static final long serialVersionUID = 0;
//保存任务所需要的数据
private Object threadPoolTaskData;

ThreadPoolTask(Object tasks){
this.threadPoolTaskData = tasks;
}
public void run(){
//处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句
System.out.println("start .."+threadPoolTaskData);
try {
便于观察,等待一段时间
Thread.sleep(consumeTaskSleepTime);
} catch (Exception e) {
e.printStackTrace();
}
threadPoolTaskData = null;
}
public Object getTask(){
return this.threadPoolTaskData;
}
}
}
//------------------------------------------------------------
说明:
1、在这段程序中,一个任务就是一个Runnable类型的对象,也就是一个ThreadPoolTask类型的对象。

2、一般来说任务除了处理方式外,还需要处理的数据,处理的数据通过构造方法传给任务。

3、在这段程序中,main()方法相当于一个残忍的领导,他派发出许多任务,丢给一个叫 threadPool的任劳任怨的小组来做。

这个小组里面队员至少有两个,如果他们两个忙不过来,任务就被放到任务列表里面。

如果积压的任务过多,多到任务列表都装不下(超过3个)的时候,就雇佣新的队员来帮忙。但是基于成本的考虑,不能雇佣太多的队员,至多只能雇佣 4个。

如果四个队员都在忙时,再有新的任务,这个小组就处理不了了,任务就会被通过一种策略来处理,我们的处理方式是不停的派发,直到接受这个任务为止(更残忍!呵呵)。

因为队员工作是需要成本的,如果工作很闲,闲到 3SECONDS都没有新的任务了,那么有的队员就会被解雇了,但是,为了小组的正常运转,即使工作再闲,小组的队员也不能少于两个。

4、通过调整 produceTaskSleepTime和 consumeTaskSleepTime的大小来实现对派发任务和处理任务的速度的控制,改变这两个值就可以观察不同速率下程序的工作情况。

5、通过调整4中所指的数据,再加上调整任务丢弃策略,换上其他三种策略,就可以看出不同策略下的不同处理方式。

下面是C++中的:

 在什么情况下使用线程池? 

    1.单个任务处理的时间比较短 
    2.将需处理的任务的数量大 

    使用线程池的好处: 

    1.减少在创建和销毁线程上所花的时间以及系统资源的开销 
    2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。


===================================================================================================

我个人比较喜欢线程,因为线程可以同时并发处理多个请求,于是乎我在编写线程的总是以如下方式启动:

  1. ...  
  2. while(true){  
  3.     server.clientSocket=server.serverSocket.accept();  
  4.     if(server.clientSocket!=null)  
  5.     {  
  6.         Thread thread=new Thread(new MsgListener(server.clientSocket));  
  7.         thread.start();  
  8.   
  9.     }  
  10. }     
  11. ...       

没有错吗,是不是很爽,如果有多少个请求,就产生多少个线程,请像在门口待客的接待员一样,让每个客人都马上得到处理,程序绝对正确,你不信到其它地方搜索一下,启动和调用都是正确的。

可是这样的编写方式,绝对只适合小量访问的环境中,并且要保证不会有大量的并发发生才可以勉强这样写,我做过试验,如果是50个并发,还勉强可以接受,如果有1000个,也许就要丢掉一大半了,我做过如下试验:

  1. public static void main(String[] args) {  
  2.     // TODO Auto-generated method stub  
  3.     BatClient b=new BatClient();  
  4.     String transq="";  
  5.     int index=0;  
  6.     int times=100;  
  7.     for(int j=0;j<times;j++){  
  8.         for(int i=0;i<50;i++){  
  9.             transq=String.valueOf(index);  
  10.             transq=b.getZeroString(transq.length())+transq;  
  11.             Thread thread=new Thread(new Client(transq));  
  12.             thread.start();  
  13.             index++;  
  14.         }  
  15.         try {  
  16.             Thread.sleep(1000);  
  17.         } catch (InterruptedException e) {  
  18.             // TODO Auto-generated catch block  
  19.             e.printStackTrace();  
  20.         }  
  21.     }  
  22. }  

并发50,循环100次,就有一大群线程,因为缺少资源等等原因活不过来,偶尔能够全部活过来(在金融系统中千万不能够有这样的问题,少一个0就值多少钱,我一辈子的工资都不够)。

要保证全部存活,就要让多出来的线程等等,有资源的时候也处理,没有资源就在外面吹吹风吗,宁愿慢,不可以乱吗。这个时候就必须要用到线程池了,线程池是个什么样的概念,我这里简而言之,具体的请上网去查相关的概念。

线程池,就是在调用线程的时候初使化一定数量的线程,有线程过来的时候,先检测初使化的线程还有空的没有,没有就再看当前运行中的线程数是不是已经达到了最大数,如果没有,就新分配一个线程去处理,就像餐馆中吃饭一样,从里面叫一个服务员出来;但如果已经达到了最大数,就相当于服务员已经用于了,那没得办法,另外的线程就只有等了,直到有新的“服务员”为止。线程池的优点就是可以管理线程,有一个高度中枢,这样程序才不会乱,保证系统不会因为大量的并发而因为资源不足挂掉。这个时候既然知道了线程池的优点,那么我们启动线程的时候就要换一种方式了:


  1. //初使化线程池,这是JDK5.0中自带的线程池,这里的参数依次代表:  
  2. //核心线程数(最小活动线程数)  
  3. //最大线程数及并发数【这个要注意,如果你的实际发大于该数,则有些请求这个时候虽然被接收,但是去得不到处理,这个数据一定得根据实际情况而设定,如我这里设值为20,实际模拟并发50,如循环一次,或者是二次并发,总会有20个不能够处理,如果设为25,就有15得不到处理,如果设为50则全部可以被处理,这个可以折磨了我好几天】  
  4. //线程池维护线程所允许的空闲时间  
  5. //线程池维护线程所允许的空闲时间的单位  
  6. //线程池所使用的缓冲队列  
  7. //线程池对拒绝任务的处理策略(通常是抛出异常)  
  8. ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1010010,TimeUnit.SECONDS,   
  9.     new ArrayBlockingQueue<Runnable>(10),  
  10.     new ThreadPoolExecutor.DiscardOldestPolicy());  
  11. while(true){  
  12.     //侦听  
  13.     server.clientSocket=server.serverSocket.accept();  
  14.     if(server.clientSocket!=null)  
  15.     {     
  16.         //采用线程池启动线程  
  17.         threadPool.execute(new MsgListener(server.clientSocket));  
  18.     }  
  19. }  
可以看出这里的启动和上面没有多大的差别,这样写还更简洁,只是需要初使化一些必须的参数而已。

  • 12
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值