虽然线程是轻量级进程,但是如果当创建和销毁的的频率非常之高,那么它也就会消耗很多的资源。
而线程池就是用来优化线程频繁创建和销毁的场景,减少线程创建、销毁的频率。
ExecutorService
JAVA标准库为我们实现了线程池,ExecutorService是一个接口,线程池的创建并不像平常的接口实现那样直接new,而是使用了“工厂模式”。
publicstaticvoidmain(String[] args) { //创建有4个线程的线程池ExecutorServiceservice=Executors.newFixedThreadPool(4); //创建一个可以根据任务数量 来自行调整线程数量 的线程池ExecutorServiceservice1=Executors.newCachedThreadPool(); //创建含有一个线程的线程池ExecutorServiceservice2=Executors.newSingleThreadExecutor(); //创建一个含有3个线程的线程池,该线程池可以调度命令在给定时间后延迟运行ScheduledExecutorServiceservice3=Executors.newScheduledThreadPool(3); }
创建好了之后可以利用submit()方法来给里面添加任务
publicstaticvoidmain(String[] args) { //创建有4个线程的线程池ExecutorServiceservice=Executors.newFixedThreadPool(4); //添加5个任务for (inti=0; i<5; i++) { inta=i; service.submit(()->{ System.out.println(a); }); } }
但是第四种创建线程池的方法有点特殊如果你想要实现延时执行任务就需要使用schedule()方法。
publicstaticvoidmain(String[] args) { // 创建一个定时执行任务的线程池,设置核心线程数为3ScheduledExecutorServiceservice=Executors.newScheduledThreadPool(3); //打印当前时间System.out.println(System.currentTimeMillis()); // 定时执行任务,延迟2秒后开始执行service.schedule(() -> { // 执行的任务逻辑System.out.println("任务执行时间:"+System.currentTimeMillis()); }, 2, TimeUnit.SECONDS); }
上述这几个创建线程池的方法本质上都是将ThreadPoolExecutor进行了封装。
ThreadPoolExecutor
这个类有4中构造方法,可是仔细看就会发现前三种还是调用的的四种,所以本质上是只有一种。
各个参数的含义
corePoolSize
当前线程池中的核心线程数即当前线程池在空闲时含有的线程数量,也就是当前线程池包含的线程最少数量。
maximumPoolSize
当前线程池中允许存在的最大线程数。
keepAliveTime
当实际线程数大于核心线程数时,多余的空闲线程能够存活的最长时间。
unit
存活时间的单位。
NANOSECONDS:千分之一微秒;
MICROSECONDS:千分之一毫秒;
MILLISECONDS:千分之一秒;
SECONDS:秒;
MINUTES:分钟;
HOURS:小时;
DAYS:天;
workQueue
用于保存待执行任务的队列。
threadFactory
创建新线程时所用的工厂类。
handler
当线程池中的任务满了之后所使用的拒绝策略。
ThreadPoolExecutor.AbortPolicy:直接抛出异常;
ThreadPoolExecutor.CallerRunsPolicy:新添加的任务,由添加任务的线程执行;
ThreadPoolExecutor.DiscardOldestPolicy :丢弃队列中最老的任务,再将新任务添加进任务队列;
ThreadPoolExecutor.DiscardPolicy:丢弃新添加的任务。
线程池的关闭
想要关闭线程池需要使用shutdown()方法
publicstaticvoidmain(String[] args) { // 创建一个定时执行任务的线程池,设置核心线程数为3ScheduledExecutorServiceservice=Executors.newScheduledThreadPool(3); //打印当前时间System.out.println(System.currentTimeMillis()); // 定时执行任务,延迟2秒后开始执行service.schedule(() -> { // 执行的任务逻辑System.out.println("任务执行时间:"+System.currentTimeMillis()); }, 2, TimeUnit.SECONDS); }
可以看出任务执行完后程序并没有退出。
publicstaticvoidmain(String[] args) { // 创建一个定时执行任务的线程池,设置核心线程数为3ScheduledExecutorServiceservice=Executors.newScheduledThreadPool(3); //打印当前时间System.out.println(System.currentTimeMillis()); // 定时执行任务,延迟2秒后开始执行service.schedule(() -> { // 执行的任务逻辑System.out.println("任务执行时间:"+System.currentTimeMillis()); }, 2, TimeUnit.SECONDS); //主线程休眠一段时间try { Thread.sleep(2000); // 休眠2秒 } catch (InterruptedExceptione) { e.printStackTrace(); } // 关闭线程池service.shutdown(); }
接下来为了更好的理解线程池,下面是模拟实现一个含有固定线程数的线程池。
模拟实现
先创建一个类名为MyThreadPool里面含有一个属性,类型为BlockingQueue。
publicclassMyThreadPool { //队列大小为5privateBlockingQueue<Runnable>queue=newArrayBlockingQueue<>(5); }
写一个只有一个参数的有参构造方法,参数为线程池的线程数。
利用循环创建n个线程,每个线程都不断地从队列中拿任务。
publicMyThreadPool(Integern) { for (inti=0; i<n; i++) { Threadt=newThread(()->{ while(true) { try { Runnablerunnable=queue.take(); runnable.run(); } catch (InterruptedExceptione) { thrownewRuntimeException(e); } } }); t.start(); } }
写一个submit()方法可以给队列中添加任务。
publicvoidsubmit(Runnablerunnable) { try { this.queue.put(runnable); } catch (InterruptedExceptione) { thrownewRuntimeException(e); } }
此时一个简单的线程池就完成了,下面来进行一下简单的测试:
publicstaticvoidmain(String[] args) { MyThreadPoolmyThreadPool=newMyThreadPool(5); for (inti=0; i<40; i++) { inta=i; myThreadPool.submit(()->{ System.out.println(a); }); } }
完整代码
importjava.util.concurrent.ArrayBlockingQueue; importjava.util.concurrent.BlockingQueue; //线程池publicclassMyThreadPool { privateBlockingQueue<Runnable>queue=newArrayBlockingQueue<>(5); publicMyThreadPool(Integern) { for (inti=0; i<n; i++) { Threadt=newThread(()->{ while(true) { try { Runnablerunnable=queue.take(); runnable.run(); } catch (InterruptedExceptione) { thrownewRuntimeException(e); } } }); t.start(); } } publicvoidsubmit(Runnablerunnable) { try { this.queue.put(runnable); } catch (InterruptedExceptione) { thrownewRuntimeException(e); } } }