线程的复用:线程池(读书笔记)
多线程的软件设计方法确实可以最大限度的发挥现代多核心处理器的计算能力,提高生产系统的吞吐量和性能,但是若不加控制和管理的随意使用线程,对熊的性能反而产生了不力的影响.
在实际生产环境中,线程的数量必须得到控制,盲目的大量创建线程对系统性能是有伤害的.
- 什么是线程池:
为了避免系统频繁的创建和销毁线程,我们可以让创建的线程进行复用,大家对数据库连接池肯定不陌生,线程池也是一个目的,线程池中,总有那么几条活跃的线程,当你需要线程的时候,可以从池子中随便哪一个空闲线程,当完成工作时,并不着急关闭线程,而是将这个线程退回到池子,方便其他人使用.
简而言之,在使用线程池后,创建线程变成了从线程池获得空闲线程,关闭线程变成了归还线程,
- 不重复造轮子:JDK对线程池的支持
JDK提供了一套Executor框架:
以上成员均在Java.util.cocurrent包中,是JDK的核心类,其中ThreadPoolExecutor表示一个线程池,Executors类则扮演者线程池工厂的角色,通过Executors可以取得一个拥有特定功能的线程池,
Executor框架提供了各种类型的线程池,主要有以下工厂方法:
public static ExecutorService newFixedThreadPool(int nThreads) public static ExecutorService newSingleThreadExecutor() public static ExecutorService newCachedThreadPool() public static ScheduledExecutorService newSingleThreadScheduledExecutor() public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
- newFixedThreadPool()方法. 该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,当有一个新任务时,线程池中若有空闲线程,则立即执行,若没有,则新任务会被暂时存在一个队列中,得有空闲线程时,便处理在任务队列中的任务
- newSingleThreadExecutor()方法,改方法返回一个只有一个线程的线程池,若多余一个任务呗提交到该线程池,任务会被保存在一个队伍队列,带线程空闲,按先入先出的顺序执行队列中的任务,
- newCachedThreadPool()方法,该方法返回一个可根据实际情况调整线程数量的线程池.线程池数量是不确定的,但若有空闲线程可以复用,则会优先使用可以复用的线程,若所有线程均在工作,又有新的任务提交,则会创建新额线程处理任务,所有线程在当前任务执行完毕后,将返回线程池进行复用,
- newSingleThreadScheduledExecutor()方法: 改方法返回一个ScheduledExecutorService对象,线程池大小为1 这个接口在ExecutorService接口之上拓展了在给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务.
- newScheduledThreadPool()方法:改方法也返回一个ScheduledExecutorService对象 但改线程池可以指定线程数量
- 固定大小的线程池.
- 计划任务
另一个值得注意的方法是newScheduledThreadPool().它返回一个ScheduledExecutorService对象,可以根据时间需要对线程进行调度.他的一些主要方法如下:
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit); public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
于其他几个线程池不同,ScheduledExecutorService并不一定会立即安排执行任务,他其实是起到了计划任务的作用,他会在指定的时间,对任务进行调度.
方法schedule()会在给定时间,对任务进行一次调度,方法scheduleAtFixedRate()和scheduleWithFixedDelay()会对任务进行周期性的调度,但两者有小小的区别
- scheduleAtFixedRate 创建一个周期性的任务,任务开始于给定的初始延迟时间,后续任务按照给定的周期惊喜,后续一个任务将会在initialDelay+period时候执行,后续第二个任务将在initialDetay+2*period时进行,
- scheduleWithFixedDelay 创建并执行一个周期性任务,任务开始于给定的初始延迟时间,后续任务将会按照给定的延时进行,即上一个任务的结束时间到下一个任务的开始时间的时间差,
我们写具体的demo来试验:
public class ScheduledExecutorServiceDemo { public static void main(String[] args) { ScheduledExecutorService ses = Executors.newScheduledThreadPool(10); ses.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { Thread.sleep(1000); System.out.println(System.currentTimeMillis() / 1000); } catch (InterruptedException e) { e.printStackTrace(); } } }, 0, 2, TimeUnit.SECONDS); } }
执行结果
0代表立即执行 然后每个2秒执行一次. 时间间隔是2秒 如果我们把线程的休眠时间设置为8s
我们发现时间间隔变成了8s
public class ScheduledExecutorServiceDemo { public static void main(String[] args) { ScheduledExecutorService ses = Executors.newScheduledThreadPool(10); /* ses.scheduleAtFixedRate(() -> { try { Thread.sleep(1000); System.out.println(System.currentTimeMillis() / 1000); } catch (InterruptedException e) { e.printStackTrace(); } }, 0, 2, TimeUnit.SECONDS);*/ ses.scheduleWithFixedDelay(() -> { try { Thread.sleep(1000); System.out.println(System.currentTimeMillis() / 1000); } catch (InterruptedException e) { e.printStackTrace(); } }, 1, 2, TimeUnit.SECONDS); } }
执行结果
我们发现时间间隔是3s 休眠的1s+2 当我们把休眠时间改成8s 时间间隔就是10s
我们发现 scheduleAtFixedRate这个方法是 线程执行完立即执行下一次,所以当执行时间大于period时 周期就是线程的执行时间 而scheduleWithFixedDelay方法是线程执行完成后等待delay时间后在执行,所以周期是执行时间+delay