线程池是并发问题绕不开的,这辈子都绕不开,只能学会使用才能勉强运行这样子…
基本方法
- Executors.newFixedThreadPool
一个固定线程数量的线程池 - Executors.newSingleThreadExecutor
一个光棍线程池 - Executors.newCachedThreadPool
一个无上限的线程池 - Executors.newScheduledThreadPool
一个可延时可定时的线程池
以上4种是最常见的线程池,其实在Executors中还有一些看着眼生的家伙:
- Executors.newSingleThreadScheduledExecutor
一个可延时可定时的光棍线程池,组合的 - Executors.newWorkStealingPool
一个可设置并行数量的线程池,新来的 - Executors.unconfigurableExecutorService
一个负责包装其他线程池的线程池,包装后不可修改 - Executors.unconfigurableScheduledExecutorService
一个负责包装延时定时线程池的线程池,包装后不可修改
如果只需要简单的调用,不考虑更多更细的性能问题,那么知道有一个Executors类能提供一些常用的线程池就已经够用了,否则,就需要一些更深入的了解。
调用示例
- newFixedThreadPool
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建多个任务
int n = 10;
Runnable[] tasks = new Runnable[n];
for (int i = 0; i < n; i++) {
final String taskExecutionFlag = "task" + (i + 1);
tasks[i] = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "," + taskExecutionFlag);
}
};
}
testFixedThreadPool(tasks);
}
public static void testFixedThreadPool(Runnable[] tasks) {
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < tasks.length; i++) {
executor.execute(tasks[i]);
}
}
}
输出:
pool-1-thread-2,task2
pool-1-thread-3,task3
pool-1-thread-1,task1
pool-1-thread-1,task6
pool-1-thread-1,task7
pool-1-thread-1,task8
pool-1-thread-1,task9
pool-1-thread-2,task5
pool-1-thread-3,task10
pool-1-thread-4,task4
可以看到,当为newFixedThreadPool传入的参数为4时,线程池内永远只有4条线程存在,但任务的顺序是不能保证顺序执行的;尝试将tasks的数量变得较大后,在线程池正常执行情况下,线程池中线程的数量仍是固定的,这也是名为"Fixed"的由来。
- newSingleThreadExecutor
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建多个任务
int n = 10;
Runnable[] tasks = new Runnable[n];
for (int i = 0; i < n; i++) {
final String taskExecutionFlag = "task" + (i + 1);
tasks[i] = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "," + taskExecutionFlag);
}
};
}
// testFixedThreadPool(tasks);
testSingleThreadExecutor(tasks);
}
public static void testSingleThreadExecutor(Runnable[] tasks){
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < tasks.length; i++) {
executor.execute(tasks[i]);
}
}
}
输出:
pool-1-thread-1,task1
pool-1-thread-1,task2
pool-1-thread-1,task3
pool-1-thread-1,task4
pool-1-thread-1,task5
pool-1-thread-1,task6
pool-1-thread-1,task7
pool-1-thread-1,task8
pool-1-thread-1,task9
pool-1-thread-1,task10
单线程不必多说,线程池内只有一条线程存在,且任务可确保按顺序执行。有意思的是,官方也不承认一条线程的线程池能称为池,所以起名为SingleThreadExecutor而不是SingleThreadPool。
- newCachedThreadPool
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建多个任务
int n = 10;
Runnable[] tasks = new Runnable[n];
for (int i = 0; i < n; i++) {
final String taskExecutionFlag = "task" + (i + 1);
tasks[i] = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "," + taskExecutionFlag);
}
};
}
// testFixedThreadPool(tasks);
// testSingleThreadExecutor(tasks);
testCachedThreadPool(tasks);
}
public static void testCachedThreadPool(Runnable[] tasks) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < tasks.length; i++) {
executor.execute(tasks[i]);
}
}
}
输出:
pool-1-thread-1,task1
pool-1-thread-3,task3
pool-1-thread-2,task2
pool-1-thread-4,task4
pool-1-thread-6,task6
pool-1-thread-3,task7
pool-1-thread-2,task10
pool-1-thread-4,task9
pool-1-thread-6,task8
pool-1-thread-5,task5
这种线程池的特点是无上限,可重用线程,官方推荐在大量短期异步任务时使用。从输出也可以看出来,有10个任务需要执行,但线程并非财大气粗地无上限地创建10个线程来执行任务,而是复用了名为2、3、4、6的线程来执行后续的任务。
- newScheduledThreadPool
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建多个任务
int n = 10;
Runnable[] tasks = new Runnable[n];
for (int i = 0; i < n; i++) {
final String taskExecutionFlag = "task" + (i + 1);
tasks[i] = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "," + taskExecutionFlag + "," + System.currentTimeMillis());
}
};
}
// testFixedThreadPool(tasks);
// testSingleThreadExecutor(tasks);
// testCachedThreadPool(tasks);
testScheduledThreadPool(tasks);
}
public static void testScheduledThreadPool(Runnable[] tasks) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
for (int i = 0; i < tasks.length; i++) {
executor.schedule(tasks[i], 100, TimeUnit.MILLISECONDS);
}
for (int i = 0; i < tasks.length; i++) {
executor.scheduleAtFixedRate(tasks[i], 200, 100, TimeUnit.MILLISECONDS);
}
for (int i = 0; i < tasks.length; i++) {
executor.scheduleWithFixedDelay(tasks[i], 200, 100, TimeUnit.MILLISECONDS);
}
}
}
输出:
太多了,就不显示了
注意一下,需要返回ScheduledExecutorService,才能调用以下接口:
schedule(Runnable command, long delay, TimeUnit unit)
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
API的参数命名很能说明作用了,这里只需要注意scheduleAtFixedRate和scheduleWithFixedDelay的细微差别,scheduleAtFixedRate执行周期是以上一个任务执行结束为起点,而scheduleWithFixedDelay的执行周期是以上一个任务执行开始为起点,所以通常情况下,scheduleAtFixedRate的时间周期会因为任务执行的耗时变得略大。
- newWorkStealingPool
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建多个任务
int n = 10;
Runnable[] tasks = new Runnable[n];
for (int i = 0; i < n; i++) {
final String taskExecutionFlag = "task" + (i + 1);
tasks[i] = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "," + taskExecutionFlag);
}
};
}
// testFixedThreadPool(tasks);
// testSingleThreadExecutor(tasks);
// testCachedThreadPool(tasks);
// testScheduledThreadPool(tasks);
testWorkStealingThreadPool(tasks);
}
public static void testWorkStealingThreadPool(Runnable[] tasks){
ExecutorService executor = Executors.newWorkStealingPool(4);
for (int i = 0; i < tasks.length; i++) {
executor.execute(tasks[i]);
}
}
}
输出:
ForkJoinPool-1-worker-1,task1
ForkJoinPool-1-worker-1,task5
ForkJoinPool-1-worker-1,task6
ForkJoinPool-1-worker-1,task7
ForkJoinPool-1-worker-1,task8
ForkJoinPool-1-worker-1,task9
ForkJoinPool-1-worker-1,task10
ForkJoinPool-1-worker-2,task2
ForkJoinPool-1-worker-0,task4
ForkJoinPool-1-worker-3,task3
这是一个不正经的线程池,看命名就知道,workstealing,从工作中窃取资源,上面的输出算是比较正常的情况,因为它“窃取成功"了,但有时候会因为系统资源“窃取失败”,导致线程无法正常创建,以致于整个线程池被意外终止,所以…建议正常情况下不要用这种线程池。
就简单的使用来说,知道API就足够用了,多用多试错,慢慢就会熟悉了。
(示例代码中为了明了用的Thread.currentThread().getName(),如果需要准确地查看某些线程是否复用,可以使用Thread.currentThread().hashCode())
以上。