先来看一个最基本的多线程创建和启动:
class SomeThead extends Thraad {
public void run() {
//do something here
}
}
public static void main(String[] args){
SomeThread oneThread = new SomeThread();
oneThread.start();
}
实现Runnable的方法大同小异,就不写了。
这样会带来几个问题:
- 使用线程的时候就去创建一个线程如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率。
- 线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况。
- 不能对线程进行简单的管理等。
针对这些问题, Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。
那么,我们应该如何创建一个线程池呢?
Java中已经提供了创建线程池的一个类:Executor
而我们创建时,一般使用它的子类:ThreadPoolExecutor.
对线程池的配置,就是对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)
首先不要被这么多个参数给吓到了,实际上也就七种类型,请听慢慢道来:
一,int corePoolSize => 该线程池中核心线程数最大值
核心线程:当有新的线程创建的时候,如果当前线程总数没达到这个值,那新建的就是核心线程,否则就是非核心线程。
二,int maximumPoolSize => 该线程池中线程总数最大值
线程总数 = 核心线程数 + 非核心线程数,就是这么好理解。
三,long keepAliveTime => 该线程池中非核心线程闲置超时时长
对非核心线程起作用,如果闲置状态的时长超过这个参数所设定的时长,就会被销毁掉
四,TimeUnit unit => keepAliveTime的单位
TimeUnit是一个枚举类型:里面包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天
再用一张图来表示这几个参数的关系:
线程池(有颜色的区域)中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收,maximumPoolSize就是线程池中可以容纳的最大线程的数量。
到这里,前四个参数应该已经相当清楚了吧,继续。
五,BlockingQueue<Runnable> workQueue
=> 该线程池中的任务队列(阻塞队列):维护着等待执行的Runnable对象
当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。用得最多的有以下三种:
- ArrayBlockingQueue:
可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
- LinkedBlockingQueue:
这个队列的总线程数被corePoolSize所限制,如果有新任务进来,核心线程已满,则需要等待,导致maximumPoolSize在这个队列中不起作用。
- SynchronousQueue:
这个队列接收到任务的时候,会直接提交给线程处理,如果所有线程都忙,那就新建线程来处理,此时maximumPoolSize应指定为Integer.MAX_VALUE为无限大。
六,ThreadFactory threadFactory
=> 创建线程的方式,这是一个接口
public interface ThreadFactory {
Thread newThread(Runnable r);
}
new 它的时候需要实现newThread方法,一般用不上。
七,RejectedExecutionHandler handler
=> 就是抛出异常专用的,比如上面提到的两个错误发生了,就会由这个handler抛出异常,不指定也有个默认的,所以,完全可以不填。
至此,我们可以创建出一个能用的线程池啦:
核心线程:10
线程池大小:1000
等待存活时间:0,单位毫秒
阻塞队列:ArrayBlockingQueue
ArrayBlockingQueue<Runnable> threadQueue = new ArrayBlockingQueue<>(1000);
ThreadPoolExecutor executor
= new ThreadPoolExecutor(10,1000,0, TimeUnit.MILLISECONDS, threadQueue);
说完了参数,又创建了一个线程池,我们看看咋个用:
首先,在ThreadPoolExecutor类中有几个非常重要的方法:
execute()
submit()
shutdown()
shutdownNow()
一,向线程池提交任务:execute()
通过ThreadPoolExecutor.execute(Runnable command)
方法即可向线程池内添加一个任务。
你可以这样写:
executor.execute(new Runnable() {
@Override
public void run() {
// do sth.
}
});
也可以把任务单独拆出来写成一个Task类,去实现Runnable方法。(推荐这样写)
二,向线程池提交任务2:submit()
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。这里暂不讨论,以后有机会再说。
三,关闭线程池:shutdown()和shutdownNow()
也是在ExecutorService中声明的方法。
shutdown调用后,不可以再submit新的task,已经submit的将继续执行。
shutdownNow试图停止当前正执行的task,并返回尚未执行的task的list。
换句话说,shutdown只是起到通知的作用,通知不再接受新的任务。而后者则会中断所有线程。所以,关闭线程可以这么写:
try {
// 通知需要停止
executor.shutdown();
// 给一个线程最后的执行时间
if (executor.awaitTermination(10,TimeUnit.MILLISECONDS)){
// 中断所有线程
executor.shutdownNow();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
至此,你已经可以用ThreadPoolExecutor去创建一个线程池,往里提交任务,然后再关闭了!