多线程的程序的确能发挥多核处理器的性能。虽然与进程相比,线程轻量化了不少,可是其建立和关闭一样须要花费时间。并且线程多了之后,也会抢占内存资源。若是不对线程加以管理的话,是一个很是大的隐患。而线程池的目的就是管理线程。当你须要一个线程时,你就能够拿一个空闲线程去执行任务,当任务执行完后,线程又会归还到线程池。这样就有效的避免了重复建立、关闭线程和线程数量过多带来的问题。java
注:摘自《实战Java高并发程序设计》多线程
如图是Java并发包下提供的线程池功能。其中ExecutorService接口提供一些操做线程池的方法。而Executors至关于一个线程池工厂类,它里面有几种现成的具有某种特定功能的线程池工厂方法。看到这些应该不陌生,举个咱们平时最常使用的例子:并发
//建立一个大小为10的固定线程池 ExecutorService threadpool= Executors.newScheduledThreadPool(10);
下面简单介绍一下这些工厂方法:ide
newFixedThreadPool()方法:固定线程数量线程池。传入的数字就是线程的数量,若是有空闲线程就去执行任务,若是没有空闲线程就会把任务放到一个任务队列,等到有线程空闲时便去处理队列中的任务。函数
newSingleThreadExecutor()方法:只有一个线程的线程池。一样,超出的任务会被放到任务队列,等这个线程空闲时就会去按顺序处理。高并发
newCachedThreadPool()方法:能够根据实际状况拓展的线程池。当没有空闲线程去执行新任务时,就会再建立新的线程去执行任务,执行完后新建的线程也会返回线程池进行复用。oop
newSingleThreadScheduledExecutor()方法:返回的是ScheduledExecutorService对象。ScheduledExecutorService是继承于ExecutorService的,有一些拓展方法,如指定执行时间。这个线程池大小为1,在指定时间执行任务。关于指定时间的几个方法:schedule()是在指定时间后执行一次任务。scheduleAtFixedRate()和方法scheduleWithFixedDelay()方法,二者都是周期性的执行任务,可是前者是以上一次任务开始为周期起点,后者是以上一次任务结束为周期起点。具体的参数你们能够在IDE里面查看。性能
newScheduledThreadPool()方法:和上面一个方法同样,可是能够指定线程池大小,其实上面那个方法也是调用这个方法的,只是传入的参数是1。this
上面简单的对Java并发包下线程池的结构和API进行简单的介绍,下面开始深刻了解一下线程池。若是你们在IDE上追踪一下上面几个工厂方法就会发现,其中最后都会调用一个方法,经过上图其实也能够发现。那就是ThreadPoolExecutor的构造方法,工厂方法只是帮咱们传入不一样的参数,从而实现不一样的效果,因此若是你想更自由的控制本身的线程池,推荐直接使用ThreadPoolExecutor建立线程池。下面给出这个构造函数的参数列表:spa
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
参数从上到下,做用依次为:
1.指定线程池种线程的数量。
2.线程池种最大的线程数量,也就是最大能拓展到多少。
3.当线程数量超过corePoolSize,多余的空闲线程多久会被销毁。
4.keepAliveTime的单位。
5.任务队列,当空闲线程不够,也不能再新建线程时,新提交的任务就会被放到任务队列种。
6.线程工厂,用于建立线程,默认的便可。
7.拒绝策略。当任务太多,达到最大线程数量、任务队列也满了,该如何拒绝新提交的任务。
任务队列是一个BlockingQueue接口,在ThreadPoolExecutor一共有以下几种实现类实现了BlockingQueue接口。
SynchronousQueue:直接提交队列。这种队列其实不会真正的去保存任务,每提交一个任务就直接让空闲线程执行,若是没有空闲线程就去新建,当达到最大线程数时,就会执行拒绝策略。因此使用这种任务队列时,通常会设置很大的maximumPoolSize,否则很容易就执行了拒绝策略。newCachedThreadPool线程池的corePoolSize为0,maximumPoolSize无限大,它用的就是直接提交队列。
ArrayBlockingQueue:有界任务队列,其构造函数必须带一个容量参数,表示任务队列的大小。当线程数量小于corePoolSize时,有任务进来优先建立线程。当线程数等于corePoolSize时,新任务就会进入任务队列,当任务队列满了,才会建立新线程,线程数达到maximumPoolSize时执行拒绝策略。
LinkedBlockingQueue:无界任务队列,经过它的名字也应该知道了,它是个链表,除非没有空间了,否则不会出现任务队列满了的状况,可是很是耗费系统资源。和有界任务队列同样,线程数若小于corePoolSize,新任务进来时没有空闲线程的话就会建立新线程,当达到corePoolSize时,就会进入任务队列。会发现没有maximumPoolSize什么事,newFixedThreadPool固定大小线程池就是用的这个任务队列,它的corePoolSize和maximumPoolSize相等。
PriorityBlockingQueue:优先任务队列,它是一个特殊的无界队列,由于它总能保证高优先级的任务先执行。
JDK提供了四种拒绝策略。
AbortPolicy:直接抛出异常,阻止系统正常工做。
CallerRunsPolicy:若是线程池未关闭,则在调用者线程里面执行被丢弃的任务,这个策略不是真正的拒绝任务。好比咱们在T1线程中提交的任务,那么该拒绝策略就会把多余的任务放到T1线程执行,会影响到提交者线程的性能。
DiscardOldestPolicy:该策略会丢弃一个最老的任务,也就是即将被执行的任务,而后再次尝试提交该任务。
DiscardPolicy:直接丢弃多余的任务,不作任何处理,若是容许丢弃任务,这个策略是最好的。
以上内置的拒绝策略都实现了RejectedExecutionHandler接口,因此上面的拒绝策略没法知足你的要求,能够自定义一个:继承RejectedExecutionHandler并实现rejectedExecution方法。
线程池中的线程是由ThreadFactory负责建立的,通常状况下默认就行,若是有一些其余的需求,好比自定义线程的名称、优先级等,咱们也能够利用ThreadFactory接口来自定义本身的线程工厂:继承ThreadFactory并实现newThread方法。
在ThreadPoolExecutor中有三个扩展方法:分别会在任务执行前beforeExecute、执行完成afterExecute、线程池退出时执行terminated。
这几个方法在哪调用的?在ThreadPoolExecutor中有一个内部类:Worker,每一个线程的任务其实都是由这个类里面的run方法执行的,贴一下这个类的源码:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable { /** * This class will never be serialized, but we provide a * serialVersionUID to suppress a javac warning. */ private static final long serialVersionUID = 6138294804551838833L; /** Thread this worker is running in. Null if factory fails. */ final Thread thread; /** Initial task to run. Possibly null. */ Runnable firstTask; /** Per-thread task counter */ volatile long completedTasks; //....省略 /** Delegates main run loop to outer runWorker */ public void run() { runWorker(this); } //....省略 }
接着进入这个runWorker方法:
final void runWorker(Worker w) { //...省略 try { //任务执行前 beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { //任务执行完 afterExecute(task, thrown); } } //....省略 }
还有一个线程池退出时执行的方法是在何处执行的?这个方法被调用的地方就不止一处了,像线程池的shutdown方法就会调用
public void shutdown() { //....省略。这个方法里面就会调用terminated tryTerminate(); }
ThreadPoolExecutor中这三个方法默认是没有任何内容的,因此咱们要自定义它也很简单,直接重写它们就好了:
ExecutorService threadpool= new ThreadPoolExecutor(5,5,0L,TimeUnit.SECONDS,new LinkedBlockingDeque<>()){ @Override protected void beforeExecute(Thread t, Runnable r) { //执行任务前 } @Override protected void afterExecute(Runnable r, Throwable t) { //执行任务后 } @Override protected void terminated() { //线程退出 } };