003-多线程-JUC线程池-几种特殊的ThreadPoolExecutor【newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool】
一、概述
在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:
以下方法是Executors下的静态方法,Executors中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。
Executors只是一个工厂类,它所有的方法返回的都是ThreadPoolExecutor
、ScheduledThreadPoolExecutor
这两个类的实例。一共可以创建四种线程池。
1.1、基础类
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; public class TestThread implements Runnable { // 线程私有属性,创建线程时创建 private Integer num = 0; public TestThread(Integer num) { this.num = num; } @Override public void run() { SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); System.out.println("thread:" + Thread.currentThread().getName() + ",time:" + sdf1.format(new Date()) + ",num:" + num); try { //使线程睡眠,模拟线程阻塞情况 TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("task "+num+"执行完毕"); } }
0、使用ThreadPoolExecutor 创建使用
public static void testThreadPoolExecutor() { ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5)); for (int i = 0; i < 15; i++) { TestThread myTask = new TestThread(i); executor.execute(myTask); System.out.println("线程池中线程数目:" + executor.getPoolSize() + ",队列中等待执行的任务数目:" + executor.getQueue().size() + ",已执行玩别的任务数目:" + executor.getCompletedTaskCount()); } executor.shutdown(); }
输出:
/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=54880:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/lib/tools.jar:/Users/lihongxu6/IdeaProjects/java-data-structure-algorithm/data-005-jdkstruct/target/classes:/Users/lihongxu6/.m2/repository/junit/junit/4.11/junit-4.11.jar com.github.bjlhx15.datastructure.algorithm.thread.ThreadPoolDemo2 线程池中线程数目:1,队列中等待执行的任务数目:0,已执行完的任务数目:0 线程池中线程数目:2,队列中等待执行的任务数目:0,已执行完的任务数目:0 线程池中线程数目:3,队列中等待执行的任务数目:0,已执行完的任务数目:0 线程池中线程数目:4,队列中等待执行的任务数目:0,已执行完的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:1,已执行完的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:2,已执行完的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:3,已执行完的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:4,已执行完的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:5,已执行完的任务数目:0 线程池中线程数目:6,队列中等待执行的任务数目:5,已执行完的任务数目:0 线程池中线程数目:7,队列中等待执行的任务数目:5,已执行完的任务数目:0 线程池中线程数目:8,队列中等待执行的任务数目:5,已执行完的任务数目:0 线程池中线程数目:9,队列中等待执行的任务数目:5,已执行完的任务数目:0 线程池中线程数目:10,队列中等待执行的任务数目:5,已执行完的任务数目:0 thread:pool-1-thread-6,time:2019-06-19 06:10:53,num:10 thread:pool-1-thread-2,time:2019-06-19 06:10:53,num:1 thread:pool-1-thread-7,time:2019-06-19 06:10:53,num:11 thread:pool-1-thread-5,time:2019-06-19 06:10:53,num:4 thread:pool-1-thread-9,time:2019-06-19 06:10:53,num:13 thread:pool-1-thread-8,time:2019-06-19 06:10:53,num:12 thread:pool-1-thread-4,time:2019-06-19 06:10:53,num:3 thread:pool-1-thread-10,time:2019-06-19 06:10:53,num:14 thread:pool-1-thread-1,time:2019-06-19 06:10:53,num:0 thread:pool-1-thread-3,time:2019-06-19 06:10:53,num:2 task 11执行完毕 task 14执行完毕 task 12执行完毕 task 0执行完毕 task 2执行完毕 task 3执行完毕 task 13执行完毕 task 1执行完毕 task 10执行完毕 task 4执行完毕 thread:pool-1-thread-3,time:2019-06-19 06:10:56,num:9 thread:pool-1-thread-1,time:2019-06-19 06:10:56,num:8 thread:pool-1-thread-10,time:2019-06-19 06:10:56,num:7 thread:pool-1-thread-8,time:2019-06-19 06:10:56,num:6 thread:pool-1-thread-7,time:2019-06-19 06:10:56,num:5 task 8执行完毕 task 5执行完毕 task 7执行完毕 task 9执行完毕 task 6执行完毕 Process finished with exit code 0
1.2、线程池的submit和execute方法区别
1、接收的参数不一样
2、submit有返回值,而execute没有
3、submit方便Exception处理
意思就是如果你在你的task里会抛出checked或者unchecked exception,
而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。
1.3、用法统一说明
理解:001-多线程-基础-进程线程、线程状态、优先级、用户线程和守护线程
java中线程分为两种类型:用户线程和守护线程。通过Thread.setDaemon(false)设置为用户线程;通过Thread.setDaemon(true)设置为守护线程。如果不设置此属性,默认为用户线程。
用户线程和守护线程的区别:【用户线程存活程序存活,其他则结束】
故创建后会等待结果
注意点一、单元测试,如果不加Thread.sleep(3000)会直接退出,或者没有Future get 也会直接退出
用户线程为什么直接退出?
单元测试核心类:TestRunner
public class TestRunner extends BaseTestRunner { private ResultPrinter fPrinter; public static final int SUCCESS_EXIT = 0; public static final int FAILURE_EXIT = 1; public static final int EXCEPTION_EXIT = 2; //…… public static void main(String[] args) { TestRunner aTestRunner = new TestRunner(); try { TestResult r = aTestRunner.start(args); if (!r.wasSuccessful()) { System.exit(1); } System.exit(0); } catch (Exception var3) { System.err.println(var3.getMessage()); System.exit(2); } }
查看TestResult
/** * Returns whether the entire test was successful or not. * 返回整个测试是否成功。 */ public synchronized boolean wasSuccessful() { return failureCount() == 0 && errorCount() == 0; }
可以看到:当aTestRunner调用start方法后不会去等待子线程执行完毕在关闭主线程,而是直接调用TestResult.wasSuccessful()方法,而这个方法始终返回的是false,所以主线程接下来就会执行System.exit,这个放回会结束当前运行的jvm虚拟机,所以使用junit测试多线程方法的结果异常就正常了;
想要正常输出的话可以让主线程不要结束,等待子线程全部运行结束后在结束主线程,输出结果就会正常。
使用:1、Thread.sleep(2000);或者 2、future.get()阻塞 或者 3、直接使用main方法测试
注意点二、方法内调用多线程方法,方法结束,多线程不结束
web中Controller请求
//容器主线程持续运行,程序会立即返回,然后后台默默执行开启的线程 @RequestMapping(value = "/poolSubmit", method = RequestMethod.GET) @ResponseBody public Object poolSubmit() { for (int i = 0; i < 10; i++) { fixedThreadPool.execute(() -> { try { Thread.sleep(2000); System.out.println(new Date()); } catch (InterruptedException e) { e.printStackTrace(); } }); } fixedThreadPool.shutdown(); return "ok"; }
上述可能拿不到结果,如果需要结果,推荐使用submit带返回Future的get阻塞方式,如下
//容器主线程 持续运行,程序阻塞 @RequestMapping(value = "/poolSubmitGet", method = RequestMethod.GET) @ResponseBody public Object poolSubmitGet() { List<Future<String>> futureList = new ArrayList<>(); for (int i = 0; i < 15; i++) { Future<String> submit = fixedThreadPool.submit(() -> { try { System.out.println("======-----------------------------" + new Date()); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return "ok:" + new Date(); }); futureList.add(submit); } for (Future<String> future : futureList) { try { System.out.println(future.get() + "ok"); } catch (Exception e) { e.printStackTrace(); } } return "ok"; }
相当于:便于理解web请求
public static void main(String[] args) { System.out.println(getTest()); //程序立即打印 Ok //同时 都是用户线程 会等方法中的全部线程执行完毕后退出 } public static String getTest(){ ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { fixedThreadPool.submit(()->{ try { Thread.sleep(2000); System.out.println(new Date()+":"+Thread.currentThread().getName() +";守护线程:"+Thread.currentThread().isDaemon()); } catch (InterruptedException e) { e.printStackTrace(); } }); } fixedThreadPool.shutdown(); return "ok"; }
二、内置工具类库
2.1、newFixedThreadPool //创建固定容量大小的缓冲池
构造一个固定线程数目的线程池,配置的corePoolSize与maximumPoolSize大小相同,同时使用了一个无界LinkedBlockingQueue存放阻塞任务,因此多余的任务将存在再阻塞队列,不会由RejectedExecutionHandler处理 ,容易OOM
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
public static void main(String[] args) { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); for (int i = 0; i < 50; i++) { fixedThreadPool.submit(new TestThread((i + 1))); } fixedThreadPool.shutdown(); }
2、newCachedThreadPool //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE,空闲60s销毁
构造一个缓冲功能的线程池,配置corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,keepAliveTime=60s,以及一个无容量的阻塞队列 SynchronousQueue,因此任务提交之后,将会创建新的线程执行;线程空闲超过60s将会销毁
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
newCachedThreadPool比较适合没有固定大小并且比较快速就能完成的小任务,没必要维持一个Pool,这比直接new Thread来处理的好处是能在60秒内重用已创建的线程。
其他类型的ThreadPool看看构建参数再结合上面所说的特性就大致知道它的特性。该线程池不会对线程数目加以限制,完全依赖于JVM能创建线程的数量,可能引起内存不足。
newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
public static void main(String[] args) { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 50; i++) { cachedThreadPool.submit(new TestThread((i + 1))); } cachedThreadPool.shutdown(); }
3、newSingleThreadExecutor //创建容量为1的缓冲池
构造一个只支持一个线程的线程池,配置corePoolSize=maximumPoolSize=1,无界阻塞队列LinkedBlockingQueue;保证任务由一个线程串行执行
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
newSingleThreadExecutor返回以个包含单线程的Executor,将多个任务交给此Exector时,这个线程处理完一个任务后接着处理下一个任务,若该线程出现异常,将会有一个新的线程来替代。它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;
从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。
实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。
另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。
public static void main(String[] args) { ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); for (int i = 0; i < 50; i++) { singleThreadPool.submit(new TestThread((i + 1))); } singleThreadPool.shutdown(); }
4、newScheduledThreadPool
可调度线程池
造有定时功能的线程池,配置corePoolSize,无界延迟阻塞队列DelayedWorkQueue;有意思的是:maximumPoolSize=Integer.MAX_VALUE,由于DelayedWorkQueue是无界队列,所以这个值是没有意义的
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); } public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) { super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS, new DelayedWorkQueue(), threadFactory); }
实例
public static void main(String[] args) { ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); //定时 // scheduledThreadPool.schedule(new Runnable() { // @Override // public void run() { // SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); // System.out.println("thread:" + Thread.currentThread().getName() + ",time:" + sdf1.format(new Date())); // } // }, 3, TimeUnit.SECONDS); //循环周期执行 scheduledThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("delay 1 seconds, and excute every 3 seconds"); } }, 1, 3, TimeUnit.SECONDS); }