#JUC学习之线程池的使用
##为什么要使用线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,那有没有方法可以使线程复用呢?就像数据库连接池一样,在Java中可以通过线程池来达到这样的效果。线程池能有效的处理多个线程的并发问题,避免大量的线程因为互相强占系统资源导致阻塞现象,能够有效的降低频繁创建和销毁线程对性能所带来的开销。
##线程池的好处
1、降低资源消耗;
2、提高响应速度;
3、提高线程的可管理性。
##线程池的核心-ThreadPoolExecutor类
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。接下来看一下线程池的一个demo,根据demo深入学习ThreadPoolExecutor的源码:
线程类
/**
* 线程执行
* @author meng
*
*/
public class MyRunnable implements Runnable {
// 正在执行的任务数
private int num;
public MyRunnable(int num) {
this.num = num;
}
@Override
public void run() {
System.out.println("正在执行的MyRunnable " + num);
try {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MyRunnable " + num + "执行完毕");
}
}
线程池
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池
* @author meng
*
*/
public class ThreadPoolExecutorTest {
//池中所保存的线程数,包括空闲线程。
final static int corePoolSize = 5;
//池中允许的最大线程数。
final static int maximumPoolSize = 10;
//当线程数大于核心线程时,此为终止前多余的空闲线程等待新任务的最长时间
final static long keepAliveTime = 200;
//执行前用于保持任务的队列5,即任务缓存队列
final static ArrayBlockingQueue<Runnable> workQueue =new ArrayBlockingQueue<Runnable>(5);
public static void main(String[] args) {
//构建一个线程池,正常线程数量为5,最大线程数据为10,等待时间200
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MINUTES, workQueue);
//线程池去执行15个任务
for (int i = 0; i < 15; i++) {
MyRunnable myRunnable = new MyRunnable(i);
threadPoolExecutor.execute(myRunnable);
System.out.println("线程池中现在的线程数目是:"+threadPoolExecutor.getPoolSize()+", 队列中正在等待执行的任务数量为:"+
threadPoolExecutor.getQueue().size());
}
//关掉线程池
threadPoolExecutor.shutdown();
}
//文章最后附赠另一种线程池配置方法
}
执行结果
线程池中现在的线程数目是:1, 队列中正在等待执行的任务数量为:0
正在执行的MyRunnable 0
线程池中现在的线程数目是:2, 队列中正在等待执行的任务数量为:0
线程池中现在的线程数目是:3, 队列中正在等待执行的任务数量为:0
正在执行的MyRunnable 1
线程池中现在的线程数目是:4, 队列中正在等待执行的任务数量为:0
线程池中现在的线程数目是:5, 队列中正在等待执行的任务数量为:0
线程池中现在的线程数目是:5, 队列中正在等待执行的任务数量为:1
线程池中现在的线程数目是:5, 队列中正在等待执行的任务数量为:2
正在执行的MyRunnable 2
线程池中现在的线程数目是:5, 队列中正在等待执行的任务数量为:3
线程池中现在的线程数目是:5, 队列中正在等待执行的任务数量为:4
线程池中现在的线程数目是:5, 队列中正在等待执行的任务数量为:5
正在执行的MyRunnable 3
线程池中现在的线程数目是:6, 队列中正在等待执行的任务数量为:5
线程池中现在的线程数目是:7, 队列中正在等待执行的任务数量为:5
正在执行的MyRunnable 10
正在执行的MyRunnable 4
线程池中现在的线程数目是:8, 队列中正在等待执行的任务数量为:5
线程池中现在的线程数目是:9, 队列中正在等待执行的任务数量为:5
正在执行的MyRunnable 12
线程池中现在的线程数目是:10, 队列中正在等待执行的任务数量为:5
正在执行的MyRunnable 11
正在执行的MyRunnable 13
正在执行的MyRunnable 14
从执行结果来看,当线程池中线程的数量大于5时,便将任务放入任务缓存队列里面,任务缓存队列满了以后,变创建新的线程,来执行,如果让任务数量大于15,程序就会报错(因为设置的最大线程数是10,队列设置的5,也就是可以等待的任务数量是5,所以该线程池最大可接受15个线程并发),抛出任务拒绝异常,这时可以增大任务缓存队列,有兴趣的同学可以去试试。
接下来我们看一下ThreadPoolExecutor的源码:
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
//在ThreadPoolExecutor类中提供了四个构造方法:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
构造方法中的参数详解:
-
**corePoolSize:**核心池的大小,默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
-
**maximumPoolSize:**线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
-
**keepAliveTime:**表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
-
**unit:**参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性
-
**workQueue:**一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
- **threadFactory:**线程工厂,主要用来创建线程;
- **handler:**表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务