线程池的入门
一、为什么选择线程池
生产上的Web服务器、数据库服务器、文件服务器的使用场景,大多数都是处理来自一些远程的大量短小的任务。
不管请求如何到达服务器,服务器都是在处理这些单个处理时间很短,但是待处理的量很大的数据。这样做是很浪费
服务器资源的,因为每当一个请求到达服务器,服务器就会创建一个新的线程,在新线程中处理请求。但是由于请求
的处理时间很短、量确很大,服务器要为每次“短”的请求来进行线程的创建和销毁,这个时间就会远远的大于处理请求
的时间,这对服务器的处理效率和资源都是一种极大的浪费。
而线程池则能很好的解决这个问题,它可以很好的解决在创建和销毁线程上所花的时间以及系统资源的开销和线程
之间的无用切换问题。
二、线程池的简单使用
在Java里线程池的根接口是Executors。它本身并不能算是一个线程池,只是一个执行者。而它的子接口ExecutorService
才是真正的线程池接口。Java通过Executors提供了四种线程池:
* newCachedThreadPool
创建一个可创建新线程的线程池,在以前构造的线程可用时将重用它们,增加那些执行短期异步任务的程序性能;
如果现有线程没有可用的,则创建一个新线程并添加到池中;如果缓存中的已有线程60秒钟没有被使用,则从缓存中终止
并移除他们,以保证长时间处于空闲状态的线程池不会使用任何资源。
可以使用newCachedThreadPool(ThreadFactory threadFactory)有参构造,在需要创建新线程时,使用提供的
ThreadFactory创建。
源码:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
栗子:
自定义线程类:
public class MyThread extends Thread{ @Override public void run() { System.out.println("线程"+Thread.currentThread().getName()+"执行中"); } }
CachedThreadPool:
public class CacheThreadPoolTest { public static void main(String[] args) { //创建一个可创建新线程的线程池 ExecutorService pool = Executors.newCachedThreadPool(); //创建线程对象 MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); MyThread t4 = new MyThread(); //执行线程池 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.shutdown(); } }
运行结果:
* newFixedThreadPool
创建一个可重用的、固定线程数的线程池,以共享的无界队列方式来运行这些线程。
ps:无界队列 - 与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。当有新的任
务到来,系统的线程数小于核心池大小时,则新建线程执行任务。当达到corePoolSize后,就不会继续增加。若后续仍有新的
任务加入,而没有空闲的线程资源,则任务直接进入队列等待。如果执行期间由于某种原因导致任何线程终止,有需要的话新
线程将代替它执行后续的操作。若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存。
而newFixedThreadPool(int nThreads,ThreadFactory threadFactory)有参构造,则可以设置池中的线程数和创建
新线程时使用的线程工厂。
源码:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
栗子:
public class FixedThreadPoolTest { public static void main(String[] args) { //创建一个可重用的、固定线程数的线程池 ExecutorService pool = Executors.newFixedThreadPool(3); //创建线程对象 MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); MyThread t4 = new MyThread(); //执行线程池 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.shutdown(); } }
运行结果:
* newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
newScheduledThreadPool(int corePoolSize,ThreadFactory threadFactory)有参构造,参数一:池中保存的线程数,空
闲线程也包括在内。参数二:创建新线程时使用的工厂。
* newSingleThreadExecutor
创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行,它可以保证顺序地执行各个任务,并且
在任意给定的时间不会有多个线程是活动的。
源码:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService( new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
栗子:
public class SingleThreadTest { public static void main(String[] args) { //创建一个可创建新线程的线程池 ExecutorService pool = Executors.newSingleThreadExecutor(); //创建线程对象 MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); MyThread t4 = new MyThread(); //执行线程池 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.shutdown(); } }
结果:
这四种线程池都直接或者间接的获取ThreadPoolExecutor实例,只是实例化时传递的参数不一样。如果java提供的这几种
线程池都不能满足我们的需求,我们可以通过自定义线程池来满足自己的需求。其构造方法为:
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
corePoolSize: 核心池的大小。 当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到
corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize: 线程池最大线程数,它表示在线程池中最多能创建多少个线程;
keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止;
unit: 时间单位,其中的常量有:天、小时、分钟等;
workQueue: 一个阻塞队列,用来存储等待执行的任务。队列可以是ArrayBlockingQueue、LinkedBlockingQueue、
SynchronousQueue;
threadFactory: 线程工厂;
handler: 当拒绝处理任务时的策略、可以为:
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
* ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务。
* ThreadPoolExecutor.CallerRunsPolicy:只要线程池不关闭,该策略直接在调用者线程中,运
行当前被丢弃的任务。
* 实现RejectedExecutionHandler接口,自定义拒绝策略。