上一篇我们详细讲解了线程池的工作原理及其基本的应用,本文主要讲解一下如何使用Executors工厂创建线程池的5种方法,在JDK1.7中只提供了4种方法(前面4种),而在JDK1.8中新增了一种方法,详细请见下文第五种方式。
1、创建固定线程数线程池
创建固定线程数的线程池一共有两个方法,分别如下所示:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
核心线程数corePoolSize等于最大线程数maximumPoolSize,没有空闲线程数,因此空闲线程的存活时间keepAliveTime设置为0,使用LinkedBlockingQueue有界队列作为线程池的工作队列(也可以认为是无界队列,存放任意个任务),队列大小为Integer.MAX_VALUE。
适用于为了满足资源管理的需求,需要限制当前线程数量的应用场景,它适用于负载比较重的服务器,任务量已知,相对耗时的任务。
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Executors工厂类
* <p>
* 创建固定线程数的线程池:
* 核心线程数corePoolSize等于最大线程数maximumPoolSize,没有空闲线程数,因此线程的存活时间keepAliveTime设置为0,
* 使用LinkedBlockingQueue有界队列作为线程池的工作队列(也可以认为是无界队列,存放任意个任务),队列大小为Integer.MAX_VALUE。
* <p>
* 适用于为了满足资源管理的需求,需要限制当前线程数量的应用场景,它适用于负载比较重的服务器,任务量已知,相对耗时的任务。
*/
public class UseExecutorsCreateThreadPool1 {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static AtomicInteger count = new AtomicInteger();
private static ExecutorService executorService = Executors.newFixedThreadPool(2, (r) -> {
return new Thread(r, "t" + count.incrementAndGet());
});
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
int finalI = i;
executorService.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务" + finalI);
});
}
}
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
}
执行结果如下:
从执行结果来看,无论主线程提交多少个任务,线程池最多会创建两个线程来执行提交的所有任务。
2、创建单个线程数的线程池
创建单个线程数的线程池一共有两个方法,分别如下所示:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
核心线程数corePoolSize等于最大线程数maximumPoolSize,没有空闲线程数,因此空闲线程的存活时间keepAliveTime设置为0,使用LinkedBlockingQueue有界队列作为线程池的工作队列(也可以认为是无界队列),队列大小为Integer.MAX_VALUE。
适用于多个任务排队有序执行,线程池中线程数固定为1,当提交的任务数大于1时,会放入阻塞队列中。区别于使用 new Thread() 创建一个单线程串行执行任务,如果创建的单线程执行任务失败而线程终止,那么后续的执行任务没有任何的补救措施,无法执行;如果使用线程池创建的线程执行任务失败而终止,线程池会再创建一个线程来执行任务,来保证线程池的正常工作。
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Executors工厂类
* <p>
* 创建单个线程数的线程池:
* 核心线程数corePoolSize等于最大线程数maximumPoolSize,没有空闲线程数,因此线程的存活时间keepAliveTime设置为0,
* 使用LinkedBlockingQueue有界队列作为线程池的工作队列(也可以认为是无界队列),队列大小为Integer.MAX_VALUE。
* <p>
* 适用于多个任务排队有序执行,线程池中线程数固定为1,当提交的任务数大于1时,会放入阻塞队列中。
* 区别于使用 new Thread() 创建一个单线程串行执行任务,如果创建的单线程执行任务失败而线程终止,那么后续的执行任务没有任何的补救措施,无法执行;
* 如果使用线程池创建的线程执行任务失败而终止,线程池会再创建一个线程来执行任务,来保证线程池的正常工作。
*/
public class UseExecutorsCreateThreadPool2 {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static AtomicInteger count = new AtomicInteger();
private static ExecutorService executorService = Executors.newSingleThreadExecutor((r) -> {
return new Thread(r, "t" + count.incrementAndGet());
});
/**
* 所有任务都由一个线程 t1 来执行
*/
private static void m1(){
for (int i = 0; i < 5; i++) {
int finalI = i;
executorService.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务" + finalI);
});
}
}
/**
* 线程池中的线程如果在执行任务的时候抛出异常,线程池会再重新创建一个线程来执行任务。
*/
private static void m2(){
executorService.execute(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务开始");
int a = 1/0;
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务结束");
});
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务");
});
}
/**
* new Thread() 创建一个单线程串行执行任务,如果创建的单线程执行任务失败而线程终止,那么后续的执行任务没有任何的补救措施,无法执行。
*/
private static void m3(){
new Thread(()->{
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务开始");
int a = 1/0;
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务结束");
},"t").start();
}
public static void main(String[] args) throws InterruptedException {
//m1();
//m2();
m3();
}
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
}
方法m1()执行结果如下:
从执行结果可知,所有任务都由一个线程 t1 来执行。
方法m2()执行结果如下:
从执行结果可知,线程池中的线程 t1 如果在执行任务的时候抛出异常,线程池会再重新创建一个线程 t2 来执行任务。
方法m3()执行结果如下:
从执行结果可知,new Thread() 创建一个单线程串行执行任务,如果创建的单线程执行任务失败而线程终止,那么后续的执行任务没有任何的补救措施,无法执行。
与上面说的第一种方式创建线程池相比,二者区别在于:
- Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改;
- Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改,对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize(int corePoolSize) 方法进行修改线程池数量;
3、创建可缓存线程的线程池
创建可缓存线程的线程池一共有两个方法,分别如下所示:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
核心线程数 corePoolSize 等于0,最大线程数 maximumPoolSize 等于Integer.MAX_VALUE,这里把 keepAliveTime 设置为60s,意味着 CachedThreadPool 中的空闲线程等待新任务的最长时间为60s,空闲线程超过60s后将会被销毁。
一个可根据需要创建新线程的线程池,如果现有线程没有可用的,则创建一个新线程并添加到池中,如果有被使用完但是还没销毁的线程,就复用该线程;如果缓存中有60s未被使用的线程,则被销毁。因此,长时间保持空闲的线程池不会使用任何资源,这种线程池比较灵活,适用于执行很多短期异步任务的程序或者负载较轻的服务器,这些线程池通常可提高程序性能。
该线程池使用 SynchronousQueue 作为工作队列,它是一个同步阻塞队列,SynchronousQueue 队列中没有任何内部容量,甚至连一个容量都没有。主线程提交的任务放到 SynchronousQueue 队列中,需要有线程取走任务后才能往队列中放下一个任务。
接下来分析一下 SynchronousQueue 队列的基本使用,详见下面代码示例。
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
*
* 同步阻塞队列:
* SynchronousQueue 队列中没有任何内部容量,甚至连一个容量都没有。提交的任务放到 SynchronousQueue 队列中,
* 需要有线程取走任务后才能往队列中放下一个任务。
*
*/
public class UseSynchronousQueue {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static SynchronousQueue<String> synchronousQueue = new SynchronousQueue();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
try {
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " begin put 第一个元素");
synchronousQueue.put("one");
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " end put 第一个元素");
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " begin put 第二个元素");
synchronousQueue.put("two");
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " end put 第二个元素");
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " begin put 第三个元素");
synchronousQueue.put("three");
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " end put 第三个元素");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
TimeUnit.SECONDS.sleep(5);
new Thread(()->{
try {
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " begin take 第一个元素");
synchronousQueue.take();
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " end take 第一个元素");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
}
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
}
执行结果如下:
从执行结果来看,SynchronousQueue 同步队列中已经存放了一个任务,如果没有线程从队列中取走任务,那么后面的任务是不能放到队列中的。示例当中线程 t1 put 了一个任务,5秒后线程 t2 从队列中取走了任务,线程 t1 才可以往队列中放第二个任务。
如果主线程提交任务的速度高于线程池中线程处理任务的速度时,线程池会不断创建新的线程。极端情况下,线程池会因为创建过多线程而耗尽CPU和内存资源。因此,该线程池比较适用于执行很多短期异步任务。
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Executors工厂类
* <p>
* 创建可缓存线程的线程池:
* 核心线程数corePoolSize等于0,最大线程数maximumPoolSize等于Integer.MAX_VALUE,这里把keepAliveTime设置为60s,
* 意味着CachedThreadPool中的空闲线程等待新任务的最长时间为60s,空闲线程超过60s后将会被销毁。
* <p>
* 一个可根据需要创建新线程的线程池,如果现有线程没有可用的,则创建一个新线程并添加到池中,
* 如果有被使用完但是还没销毁的线程,就复用该线程;如果缓存中有60s未被使用的线程,则被销毁。
* 因此,长时间保持空闲的线程池不会使用任何资源,这种线程池比较灵活,适用于执行很多短期异步任务的程序或者负载较轻的服务器,
* 这些线程池通常可提高程序性能。
* <p>
* 该线程池使用 SynchronousQueue 作为工作队列,它是一个同步阻塞队列,SynchronousQueue 队列中没有任何内部容量,
* 甚至连一个容量都没有。主线程提交的任务放到 SynchronousQueue 队列中,需要有线程取走任务后才能往队列中放下一个任务。
*/
public class UseExecutorsCreateThreadPool3 {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static AtomicInteger count = new AtomicInteger();
private static ExecutorService executorService = Executors.newCachedThreadPool((r) -> {
return new Thread(r, "t" + count.incrementAndGet());
});
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
int finalI = i;
executorService.execute(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务" + finalI);
});
}
TimeUnit.SECONDS.sleep(1);
System.out.println("============================== 线程池中的线程未超时,继续使用缓存中的线程执行任务 ================================");
for (int i = 5; i < 10; i++) {
int finalI = i;
executorService.execute(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务" + finalI);
});
}
TimeUnit.SECONDS.sleep(65); // 睡眠65s,之前线程池中的空闲线程足够到达了超时时间。
System.out.println("============================== 65s 后之前线程池中创建的线程已经超时被销毁,需要重新创建线程执行任务 ================================");
for (int i = 10; i < 15; i++) {
int finalI = i;
executorService.execute(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务" + finalI);
});
}
}
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
}
执行结果如下:
从执行结果来看,线程池一共提交了15个任务,第0 ~ 4的5个任务分别创建了5个线程来执行;主线程 sleep 1秒后继续提交第5 ~ 9的5个任务,由于线程池中缓存的线程还未被销毁,因此继续使用缓存中的线程执行任务;主线程 sleep 65秒后继续提交第10 ~ 14的5个任务,65s 后之前线程池中创建的线程已经超时被销毁,需要重新创建线程执行任务;
4、创建定时执行任务的线程池
创建一个线程池,该线程池可以在定期或者给定的延迟时间后执行任务。 Executors 可以创建 2 种类型的 ScheduledThreadPoolExecutor。
- SingleThreadScheduledExecutor。只创建一个线程的ScheduledThreadPoolExecutor。适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。
- ScheduledThreadPoolExecutor。创建若干个线程的ScheduledThreadPoolExecutor。适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。
演示第一种方式创建线程池的基本应用
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Executors工厂类
* <p>
* 创建定时执行任务的线程池:
* 创建一个线程池,该线程池可以在定期或者给定的延迟时间后执行任务。Executors 可以创建 2 种类型的 ScheduledThreadPoolExecutor。
* <p>
* 1、SingleThreadScheduledExecutor。只创建一个线程的ScheduledThreadPoolExecutor。
* 适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。
* 2、ScheduledThreadPoolExecutor。创建若干个线程的 ScheduledThreadPoolExecutor。
* 适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。
* <p>
* 演示第一种方式创建线程池的基本应用
*/
public class UseExecutorsCreateThreadPool4 {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static AtomicInteger count = new AtomicInteger();
private static ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor((r) -> {
return new Thread(r, "t" + count.incrementAndGet());
});
/**
* 延迟 delay 个 unit 时间后执行(延迟5秒后执行,只执行一次任务)
*/
private static void m1() {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务");
// 延迟 delay 个 unit 时间后执行(延迟5秒后执行,只执行一次任务)
scheduledExecutorService.schedule(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务");
}, 5, TimeUnit.SECONDS);
}
/**
* 周期性执行任务,延迟4秒后开始执行任务,隔2秒执行一次任务。
* <p>
* 创建并执行一个周期行任务,该任务在给定的初始延迟后第一次执行,随后在给定的周期内执行;
* 如果任务的任何执行遇到异常,则禁止后续执行。否则,任务将仅通过执行程序的取消或终止而终止。
* 如果该任务的任何执行时间超过了它的周期,那么后续执行可能会延迟开始,但不会并发执行。
*/
private static void m2() {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务");
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务开始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务结束。。。");
}, 4, 2, TimeUnit.SECONDS);
}
/**
* 创建并执行一个周期性操作,该操作在给定的初始延迟之后首先启用,然后在一个执行的终止和下一个执行的开始之间使用给定的延迟。
* 如果任务的任何执行遇到异常,则禁止后续执行。否则,任务将仅通过执行程序的取消或终止而终止。
* 如果该任务的任何执行时间超过了它的周期,那么后续执行会在前一个任务执行结束后间隔delay后开始,但不会并发执行。
*/
private static void m3(){
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务");
scheduledExecutorService.scheduleWithFixedDelay(()->{
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务开始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务结束。。。");
},2,2,TimeUnit.SECONDS);
}
public static void main(String[] args) throws InterruptedException {
m1();
//m2();
//m3();
}
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
}
方法m1()执行结果如下:
从执行结果来看,main 线程在执行任务后提交一个任务给线程池,延迟5秒后执行,且只执行一次任务。
方法m2()执行结果如下:
从执行结果来看,main 线程在执行任务后提交一个任务给线程池,延迟4秒后开始执行任务,隔2秒执行一次任务。创建并执行一个周期性任务,该任务在给定的初始延迟后第一次执行,随后在给定的周期内执行。如果该任务的任何执行时间超过了它的周期,那么后续执行可能会延迟开始,但不会并发执行。示例中的任务执行时长明细大于周期时长,不会按照设定的隔两秒执行一次任务,这种情况下一周期任务会延迟执行,且在上一个周期任务执行结束后立刻开始执行。
方法m3()执行结果如下:
从执行结果来看,main 线程在执行任务后提交一个任务给线程池,延迟2秒后开始执行任务,隔2秒执行一次任务。创建并执行一个周期性操作,该操作在给定的初始延迟之后首先启用,然后在一个执行的终止和下一个执行的开始之间使用给定的延迟。如果该任务的任何执行时间超过了它的周期,那么后续执行会在前一个任务执行结束后间隔delay后开始,但不会并发执行。示例中的任务执行时长明细大于周期时长,不会按照设定的隔两秒执行一次任务,这种情况下一周期任务会延迟执行,且在上一个周期任务执行结束后隔2秒开始执行,这也是跟方法 m2() 执行结果的区别所在之处。
演示第二种方式创建线程池的基本应用,定义核心线程数为2,线程池会创建多个线程来执行任务。
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Executors工厂类
* <p>
* 创建定时执行任务的线程池:
* 创建一个线程池,该线程池可以在定期或者给定的延迟时间后执行任务。Executors 可以创建 2 种类型的 ScheduledThreadPoolExecutor。
* <p>
* 1、SingleThreadScheduledExecutor。只创建一个线程的ScheduledThreadPoolExecutor。
* 适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。
* 2、ScheduledThreadPoolExecutor。创建若干个线程的 ScheduledThreadPoolExecutor。
* 适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。
* <p>
* 演示第二种方式创建线程池的基本应用,定义核心线程数为2,线程池会创建多个线程来执行任务。
*/
public class UseExecutorsCreateThreadPool5 {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static AtomicInteger count = new AtomicInteger();
private static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2,(r) -> {
return new Thread(r, "t" + count.incrementAndGet());
});
/**
* 延迟 delay 个 unit 时间后执行(延迟5秒后执行,只执行一次任务)
*/
private static void m1() {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务");
// 延迟 delay 个 unit 时间后执行(延迟5秒后执行,只执行一次任务)
scheduledExecutorService.schedule(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务");
}, 5, TimeUnit.SECONDS);
}
/**
* 周期性执行任务,延迟4秒后开始执行任务,隔2秒执行一次任务。
* <p>
* 创建并执行一个周期行任务,该任务在给定的初始延迟后第一次执行,随后在给定的周期内执行;
* 如果任务的任何执行遇到异常,则禁止后续执行。否则,任务将仅通过执行程序的取消或终止而终止。
* 如果该任务的任何执行时间超过了它的周期,那么后续执行可能会延迟开始,但不会并发执行。
*/
private static void m2() {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务");
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务开始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务结束。。。");
}, 4, 2, TimeUnit.SECONDS);
}
/**
* 创建并执行一个周期性操作,该操作在给定的初始延迟之后首先启用,然后在一个执行的终止和下一个执行的开始之间使用给定的延迟。
* 如果任务的任何执行遇到异常,则禁止后续执行。否则,任务将仅通过执行程序的取消或终止而终止。
* 如果该任务的任何执行时间超过了它的周期,那么后续执行会在前一个任务执行结束后间隔delay后开始,但不会并发执行。
*/
private static void m3(){
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务");
scheduledExecutorService.scheduleWithFixedDelay(()->{
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务开始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行任务结束。。。");
},2,2,TimeUnit.SECONDS);
}
public static void main(String[] args) throws InterruptedException {
m1();
//m2();
//m3();
}
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
}
方法m1()执行结果如下:
方法m2()执行结果如下:
方法m3()执行结果如下:
5、创建工作窃取的线程池
创建工作窃取的线程池一共有两个方法,分别如下所示:
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
newWorkStealingPool,这个是 JDK1.8 版本加入的一种线程池,Stealing 翻译为窃取的意思,它对线程池的实现和上面4种都不一样,利用操作系统的 CPU 数目或者指定数目(parallelism)作为线程并发的数量来创建一个工作窃取的线程池,底层使用 ForkJoin 来实现。
创建一个线程池,该线程池维护足够的线程来支持给定的并行级别,并可以使用多个队列来减少争夺。并行级别对应于可以参与处理任务的最大线程数,线程的实际数量可以动态地增加或减少,工作窃取线程池池不能保证提交任务的顺序执行,抢占式的执行任务。
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Executors工厂类
* <p>
* 创建工作窃取的线程池:
* 利用操作系统的CPU数目作为线程并发的数量来创建一个工作窃取的线程池,底层使用 ForkJoin 来实现。
* <p>
* 创建一个线程池,该线程池维护足够对的线程来支持给定的并行级别,并可以使用多个队列来减少争夺。
* 并行性级别对应于可以参与处理任务的最大线程数。
* 线程的实际数量可以动态地增加或减少。工作窃取线程池池不能保证提交任务的顺序执行。
* <p>
* parallelism 表示并发级别,多个线程同时在执行不同的任务。
* 如果不指定parallelism大小,默认使用Runtime.getRuntime().availableProcessors()获取操作系统的CPU个数作为并发级别。
*/
public class UseExecutorsCreateThreadPool6 {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static ExecutorService executorService = Executors.newWorkStealingPool(4);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
executorService.execute(() -> {
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " 执行任务 " + finalI);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
while (true) {
}
}
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
}
执行结果如下:
通过上面代码示例及其执行结果可知,最明显的用意就是它是一个并行的线程池,参数中传入的是一个线程并发的数量,这里和之前的4种创建线程池的方式有很明显的区别,前面4种线程池都有核心线程数、最大线程数等等,而这就使用了一个并发线程数解决问题。
如何使用 Executors 工厂创建线程池的5种方法就分析到这里啦,作者能力有限,有分析不到位的地方欢迎各位读者留言讨论,下一篇将详细讲解线程池是如何保证线程池核心线程数不会被销毁及空闲线程数会被销毁的呢?大家拭目以待吧。
备注:博主微信公众号,不定期更新文章,欢迎扫码关注。