Java线程池简析

线程池简介

我们在实现并发功能时会频繁的创建和销毁线程,这样会加大系统的开销,而线程池会缓存一定数量的线程,可以避免这样的情况,并且线程池可以对现场进行简单的管理,简化了并发编程。

ThreadPoolExecutor

Java中的线程池定义为Executor接口,实现这个接口的类为ThreadPoolExecutor,是线程池的真正实现。常用的构造方法为

1
2
3
4
5
6
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
  • corePoolSize:线程池中的核心线程数量,核心线程理论上会一直存活,就算是闲置状态。
  • maximumPoolSize:允许线程池最大容纳的线程数,一旦满了,后面新到来的任务会被阻塞。
  • keepAliveTime:线程闲置的超时时长,闲置时间超过就会被销毁,适用于非核心线程。如果ThredPoolExecutor的allowCoreThreadTimeOut设置为true,那么核心线程也会超时销毁。
  • unit:超时时长的单位,为枚举类型,常用的有毫秒 TimeUnit.MILLISECONDS、秒TimeUnit.SECONDS、分TimeUnit.MINUTES。
  • workQueue:任务队列。
  • threadFactory:提供创建线程的功能。

当任务到来的时候,如果核心线程数量没有达到最大核心线程数量,那么就创建核心线程执行任务。如果超过就将任务插入队列等待核心线程空下来。如果队列也满了,那就只有创建非核心线程(或者复用闲置非核心线程)执行任务。如果全部线程数达到最大值,拒绝执行任务。

四种线程池

FixedThreadPool

构造函数如下

1
2
3
4
5
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
  • 核心线程和线程数一样说明只有核心线程,那么线程都不会被回收,可以很快的响应任务。
  • 且队列没有没有大小限制,可以看出新到来的任务肯定不会被拒绝,因为队列不会满,所有任务都可以被执行(核心线程满了就等待)。

CachedThreadPool

构造函数如下

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
  • 没有核心线程,且线程数是int的最大值,适用于大量的任务。
  • SynchronousQueue实际上是不能插入的,所以任务到来的时候,首先没有核心线程,那么就会插入队列等待核心线程,但是这个队列不能插入,所以开启非核心线程执行,也就是说任务到来立马执行,适用于大量且耗时少的任务。

ScheduledThreadPool

构造函数如下

1
2
3
4
5
6
7
8
9
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}

SingleThreadExecutor

构造函数如下

1
2
3
4
5
6
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

就一个核心线程,线程只允许一个,等待队列没限制,也就是说任务是串行执行的,不用考虑同步的问题。

执行任务

执行Runnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {

public static void main(String[] args) {
ExecutorService exe = Executors.newCachedThreadPool();
for (int i = 0; i != 5; i++) {
exe.execute(new Task());
}
exe.shutdown();
}

}

class Task implements Runnable {

@Override
public void run() {
// do something here
}
}

通过execute执行任务,shutdown()方法可以防止新任务被提交给这个Executor,执行完之前的所有任务后会尽快退出。

执行Callable

Runnable执行任务不返回任何值,想要有返回值的话可以使用Callable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Main {

public static void main(String[] args) {
ExecutorService exe = Executors.newCachedThreadPool();
List<Future<String>> results = new ArrayList<>();
for (int i = 0; i != 5; i++) {
results.add(exe.submit(new Task()));
}
exe.shutdown();
for (Future<String> fs : results) {
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}

class Task implements Callable<String> {

public static int i = 0;

@Override
public String call() {
return i++ + "";
}
}

首先确定返回值的类型,这里是String,执行的方法是call而不是run。使用submit提交任务而不是execute,submit会返回Future的值,然后我们遍历通过get()方法拿到返回值。值得注意的是,当Future未完成的时候,get()方法会阻塞,直到完成拿到返回值,我们可以通过isDone()判断是否完成。