线程池
线程池原理和流程
- 线程等待池,即线程队列BlockingQueue
- 任务处理池,PoolWorker,即正在工作的Thread列表(HashSet<Worker>)
public class ThreadPoolTest{ public static void main(String[] args)throws InterruptedException{ /** 创建线程池: 初始化线程数2 核心线程数4 最大线程数6 任务队列最多允许1000个任务 */ final ThreadPool threadPool = new BasicThreadPool(2,6,4,1000); //定义20个任务并且提交给线程 for(int i=0;i<20;i++){ threadPool.execute(() -> { try{ TimeUnit.SECONDS.sleep(10); System.out.println(Thread.currentThread().getName() + "is running done"); }catch(){} }); } for(;;){ System.out.println(threadPool.getActioveount()); System.out.println(threadPool.getQueueSize()); System.out.println(threadPool.getCoreSize()); System.out.println(threadPool.getMaxSize()); TimeUnit.SECONDS.sleep(5); } } }
==================================================
Executors工具类创建线程池
- newSingleThreadExecutor创建一个单线程的线程池
public static void main(String[] args){ //该线程池只有一个线程在工作,相当于单线程串行执行所有任务 ExecutorService executor = Executors.newSingleThreadExecutor(); for(int i=0;i<10;i++){ final int no = i; Runnable runnable = new Runnable(){ public void run(){ try{ System.out.println("into"+no); Thread.sleep(1000L); System.out.println("end"+no); }catch(){ } } }; executor.execute(runnable); } executor.shutdown(); System.out.println("Thread Main End"); }
- newCachedThreadPool创建一个缓存池大小可根据需要伸缩的线程池
ExecutorService executor = Executors.newCachedThreadPool();
- newFixedThreadPool创建一个可重用固定线程数的线程池,以共享的无界队列方式运行这些线程
//创建一个固定大小的线程池 ExecutorService executor = Executors.newFixedThreadPool(5);
《阿里巴巴Java开发手册》中,明确禁止使用Executors创建线程池,并要求开发者直接使用ThreadPoolExector或ScheduledThreadPoolExecutor进行创建,强制开发者明确线程池的运行策略,使其对线程池的每个配置参数皆做到心中有数,以规避因使用不当而造成资源耗尽的风险
ThreadPoolExecutor创建线程池
/** corePoolSize,线程池的基本大小。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程。当需要执行的任务数大于线程池基本大小的时候就不再创建。如果调用线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。 maximumPoolSize,线程池最大的大小。如果队列满了并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务(无界任务队列,该参数无效果) keepAliveTime,线程活动保持时间,线程池的工作线程空闲后,保持存活的时间,如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程利用率 milliseconds,线程活动保持的时间单位,可选的有DAYS天,HOURS小时,MINUTES分钟………… runnableTaskQueue,任务队列。用于保存等待执行的任务的阻塞队列。 ArrayBlockingQueue数组结构的有界阻塞队列,先进先出 LinkedBlockingQueue链表结构的阻塞队列,先进先出。静态工厂Executors.newFixedThreadPool()使用该队列 SynchronousQueue不存储元素的阻塞队列,每插入一个元素必须等另一个线程调用移除操作Executors.newCachedThreadPool使用这个队列 PrioritBlockingQueue具有优先级无限阻塞队列 handler,饱和策略。当队列和线程池都满了,必须采取策略处理提交的新任务,默认AbortPolicy,表示无法处理新任务时抛出异常。 AbortPolicy 直接抛出异常 CallerRunsPolicy 只用调用者所在线程来运行任务 DiscardOldesPolicy 丢弃队列里最近的一个任务,并执行当前任务 DiscardPolicy 不处理,丢弃掉 自定义策略,实现RejectedExecutionHandler接口(如记录日志,持久化不能处理的任务) */ new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime, milliseconds,runnableTaskQueue,handler); /** 向线程池提交任务 */ threadsPool.execute(new Runnable(){ //无返回值 @Override public void run(){ } }); Future<Object> future = executor.submit(); //有返回值 try{ Obejct s = future.get(); }catch(){} /** 线程池关闭: 原理,逐个遍历调用线程的interrupt方法来中断线程,如果无法响应中断的任务可能永远复发终止。 shutdownNow:首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。(任务不一定要执行完) shutdown:只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程 只要调用了以上任意一个,isShutdown方法返回true */
ScheduleThreadPoolExecutor按时间调度来执行任务
- 延迟执行任务
public ScheduleFuture<?> shedule(Runnable command,long delay,TimeUnit unit); public <V>ScheduleFuture<V> schedule(Callable<V>callable,long delay,TimeUnit unit);
- 周期执行任务
/** 按固定频率执行,与任务本身执行时间无关 前提条件:任务执行时间必须小于间隔时间,例如间隔时间是5s,每5s执行一次任务,任务的执行时间必须小于5s */ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit); /** 按固定间隔执行,与任务本身执行时间有关。 比如:任务本身执行时间10s,间隔2s,则下一次开始执行的时间就是12s */ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
Future接口和实现Future接口的FutureTask类,代表异步计算的结果
把Runnable接口或Callable接口的实现类提交(submit)给ThreadPoolExecutor或ScheduledThreadPoolExecutor时,ThreadPoolExecutor或ScheduledThreadPoolExecutor会返回一个FutureTask对象。
FutureTask可以交给Executor执行,也可以由调用线程直接执行(FutureTask.run())根据FutureTask.run()方法被执行的时机,FutureTask可以处于下面3种状态。
- 未启动(执行FutureTask.get()方法将导致调用线程阻塞)
- 已启动(执行FutureTask.get()方法将导致调用线程阻塞)
- 已完成(执行FutureTask.get()方法将导致调用线程立即返回结果或抛出异常)
线程池注意:
- 线程池一定要在合理的单例模式下才有效,不可以放在services方法中创建线程池,因为每当这个方法被调用的时候要创建一大堆线程池。
- 线程池数量不要设置过大,请求过载发挥不了线程池的优点了
- 注意死锁问题
实际应用:
Tomcat中线程池配置
Nginx线程池
数据库连接池(阿里巴巴的Druid,提供qiang)
分布式系统中实现高并发