多线程---线程池
参考博客1:http://www.cnblogs.com/exe19/p/5359885.html
参考博客2:http://www.jianshu.com/p/87bff5cc8d8c
1 . 线程池的体系结构
java.util.concurrent.Executor [I]是一个顶层接口,在它里面只声明了一个方法void execute(Runnable command); |-- ExecutorService [I]继承了Executor,线程池的主要接口 |-- AbstractExecutorService [A]抽象类,实现了ExecutorService中的大部分方法 |-- ThreadPoolExecutor [C]线程池的主要实现类 |-- ScheduledExecutorService [I]继承了ExecutorService,负责线程调度的接口 |-- ScheduledThreadPoolExecutor[C]继承 ThreadPoolExecutor,实现 ScheduledExecutorService
2 .线程池的使用
public static void method1() { Executor pool = Executors.newFixedThreadPool(5); for(int i=0;i<5;i++){ pool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } } 1、Executors.newFixedThreadPool(5)初始化一个包含5个线程的线程池pool; 2、通过pool.execute方法提交1个任务,该任务打印当前的线程名; 3、负责执行任务的线程的生命周期都由Executor框架进行管理;
3 . ThreadPoolExecutor类
- 构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
- corePoolSize:线程池中的核心线程数。在创建了线程池后,默认情况下,线程池中的线程数为0即线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务 ( 除非调用了prestartAllCoreThreads()或者prestartCoreThread()这2个预创建线程的方法,这两个方法会在没有任务到来之前就创建corePoolSize个线程或者一个线程 )当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到阻塞缓存队列当中。
- maximumPoolSize:线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;
- keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
- unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天 TimeUnit.HOURS; //小时 TimeUnit.MINUTES; //分钟 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //纳秒
- workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
2、LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
4、priorityBlockingQuene:具有优先级的无界阻塞队列;
其中ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。 - threadFactory:线程工厂,主要用来创建线程;
- handler:线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
1、AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,默认策略;
2、DiscardPolicy:也是丢弃任务,但是不抛出异常。
3、DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4、CallerRunsPolicy:用调用者所在的线程来执行任务;
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
-
execute()方法
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * 此过程分为3步: * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * 1.如果正在运行的线程数小于corePoolSize,则尝试启动一个新 * 的线程来执行指定的任务。对addWorker 方法的调用会原子性 * 的检查runState (运行状态)和workerCount(工作集数量 ), * 通过返回false阻止不应该出现的添加错误。 * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * 2. 如果一个线程可以成功加入队列,无论我们已经将线 * 程添加进去了还是在进入这个方法时线程池已经关闭了都需 * 要继续做两次检查(因为上次检查之后可能存在一个死掉的 * 线程)所以就再次检查状态,并且如果必要就会滚加入队列的 * 操作或者在一个也没有的时候启动一个新的线程。 * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. * 如果任务无法加入队列,就尝试添加一个新的线程,如果失败, * 说明线程池关闭了或者队列已经满了所以拒绝了这次任务 */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
-
submit()方法
此方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果 -
shutdown()和shutdownNow()方法
这两个方法是用来关闭线程池的。