引言:
说说线程池吧。
一、为什么要线程池?
单个请求的时间短,同时请求数目太多。如果每个新请求到达就创建新线程,会造成频繁的创建新线程和销毁线程,使其系统非处理请求的开销(时间与资源)过多,对系统利用率降低,系统性能差,资源利用率差。
除此之外,太多的活动线程,会消耗太多系统资源,同时线程间切换也会有特别多的开销。所以服务器应用程序要限制给定时间内的处理请求数目。
二、怎么创建线程池?
首先我们需要做一个任务线程FlyThread
/** * 任务线程,用于执行单个任务 * 可以直接继承Thread实现,也可以实现Runnable接口 * 定义一个isRunning的标记位,标记当然线程是否属于运行状态 * 为了标记线程,还引入一个线程的名称 * @author Fly */ public class FlyThread implements Runnable { private boolean isRunning; private String threadname; public boolean isIsRunning() { return isRunning; } //当线程设置为true的时候,即设置为工作状态,开始执行任务,即下面run方法中的else代码块 public synchronized void setIsRunning(boolean isRunning) { this.isRunning = isRunning; if (isRunning) { this.notify(); } } public String getThreadname() { return threadname; } public void setThreadname(String threadname) { this.threadname = threadname; } public FlyThread(String threadname) { this.isRunning = false; this.threadname = threadname; } @Override public synchronized void run() { while (true) { try { //当线程已标记为非工作状态时,进入等待状态,接收任务中... if (!isRunning) { this.wait(); } else { System.out.println("线程【" + getThreadname() + "】执行中,耗时3秒钟..."); sleep(3000); //当执行任务完毕后,设置当前线程为空闲状态 setIsRunning(false); System.out.println("线程【" + getThreadname() + "】执行完毕."); } } catch (InterruptedException ex) { Logger.getLogger(FlyThread.class.getName()).log(Level.SEVERE, null, ex); } } } }然后我们需要一个ThreadPool来管理我们的线程,当然还需要一个List来装载我们的线程
/** * * @author Fly */ public final class ThreadPool { //使用单例初始化线程池 private static ThreadPool threadpool = null; //默认最大线程池大小 private static int poolMaxSize = 10; //线程池的队列,使用Vector,线程安全 private Vector<FlyThread> threadVector; private ThreadPool() { //初始化默认大小的线程池 this(poolMaxSize); } //指定大小的线程池 private ThreadPool(int poolSize) { int size = poolMaxSize > poolSize && poolSize > 0 ? poolSize : poolMaxSize; threadVector = new Vector(); while (size-- > 0) { //给线程指定线程名称 FlyThread flythread = new FlyThread("线程" + size); //添加进入任务队列 threadVector.add(flythread); //启动线程 new Thread(flythread).start(); } } public static ThreadPool getPoolInstance() { if (threadpool == null) { threadpool = new ThreadPool(); } return threadpool; } public static ThreadPool getPoolInstance(int num) { if (threadpool == null) { threadpool = new ThreadPool(num); } return threadpool; } public void execute(String str) { for (FlyThread thread : threadVector) { String cunrentThreadName = thread.getThreadname(); //线程空闲状态时,才能执行任务 if (!thread.isIsRunning()) { //设置为运行状态,并通知线程处理响应的数据。 thread.setIsRunning(true); System.out.println("线程【" + cunrentThreadName + "】空闲,操作字符:" + str); return; } else { System.out.println("线程【" + cunrentThreadName + "】忙,不操作字符:" + str); } } } }最后看线程池的使用ThreadPoolTest
/** * * @author Fly */ public class ThreadPoolTest { public static void main(String[] args) { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //初始化容量为5的线程池 ThreadPool flypool = ThreadPool.getPoolInstance(5); String str; try { while ((str = br.readLine()) != null) { //处理任务 flypool.execute(str); } } catch (IOException ex) { Logger.getLogger(ThreadPoolTest.class.getName()).log(Level.SEVERE, null, ex); } } }执行结果,比如我们在终端连续输入a,b,c,d,e,f
run: a 线程【线程4】空闲,操作字符:a 线程【线程4】执行中... b 线程【线程4】忙,不操作字符:b 线程【线程3】空闲,操作字符:b 线程【线程3】执行中... c 线程【线程4】忙,不操作字符:c 线程【线程3】忙,不操作字符:c 线程【线程2】空闲,操作字符:c 线程【线程2】执行中... d 线程【线程4】忙,不操作字符:d 线程【线程3】忙,不操作字符:d 线程【线程2】忙,不操作字符:d 线程【线程1】空闲,操作字符:d 线程【线程1】执行中... e 线程【线程4】忙,不操作字符:e 线程【线程3】忙,不操作字符:e 线程【线程2】忙,不操作字符:e 线程【线程1】忙,不操作字符:e 线程【线程0】空闲,操作字符:e 线程【线程0】执行中... f 线程【线程4】忙,不操作字符:f 线程【线程3】忙,不操作字符:f 线程【线程2】忙,不操作字符:f 线程【线程1】忙,不操作字符:f 线程【线程0】忙,不操作字符:f 线程【线程4】执行完毕. 线程【线程3】执行完毕. 线程【线程2】执行完毕. 线程【线程1】执行完毕. 线程【线程0】执行完毕.
我们发现f没有被处理,这是因为当请求数目超过池子大小的时候,没有进入池子的处理方法,被直接抛弃掉了。所以在实际的开发过程中,池子大小的设定相当重要。
三、如何设置线程池的大小?(本段摘自调整池的大小)
Java中每个线程需要大约1~2MB内存,线程开的越多,消耗的内存也就越大。
一般情况下载线程数目接近N(机器处理器个数)时,添加额外的线程数可以改善总处理能力,而在线程数目超过N时,添加额外的线程数却没有太多作用。太多的线程会降低性能,增加线程切换的开销。线程池的最佳大小取决于可用处理器的数目以及工作队列中的任务的性质。若在一个具有 N 个处理器的系统上只有一个工作队列,其中全部是计算性质的任务,在线程池具有 N 或 N+1 个线程时一般会获得最大的 CPU 利用率。
对于那些可能需要等待 I/O 完成的任务(例如,从套接字读取 HTTP 请求的任务),需要让池的大小超过可用处理器的数目,因为并不是所有线程都一直在工作。通过使用概要分析,您可以估计某个典型请求的等待时间(WT)与服务时间(ST)之间的比例。如果我们将这一比例称之为 WT/ST,那么对于一个具有 N 个处理器的系统,需要设置大约 N*(1+WT/ST) 个线程来保持处理器得到充分利用。
处理器利用率不是调整线程池大小过程中的唯一考虑事项。随着线程池的增长,您可能会碰到调度程序、可用内存方面的限制,或者其它系统资源方面的限制,例如套接字、打开的文件句柄或数据库连接等的数目。
四、利用java api自带的线程池工具(本段参考ThreadPoolExecutor详解)
Java中执行器(Executor)将为你管理Thread对象,从而简化了并发编程。Executor在客户端和执行任务之间提供了一个间接层,Executor代替客户端执行任务。Executor允许你管理异步任务的执行,而无须显式地管理线程的生命周期。Executor在Java SE5/6中时启动任务的优选方法。Executor引入了一些功能类来管理和使用线程Thread,其中包括线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。
Executors类里面提供了一些静态工厂,生成一些常用的线程池。
我们首先创建一个任务线程FlyExeThread
public class FlyExeThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "执行中..."); } }1. newSingleThreadExecutor
public class ThreadExecutorTest { public static void main(String[] args) { //创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.newSingleThreadExecutor(); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口 FlyExeThread ft1 = new FlyExeThread(); FlyExeThread ft2 = new FlyExeThread(); FlyExeThread ft3 = new FlyExeThread(); FlyExeThread ft4 = new FlyExeThread(); FlyExeThread ft5 = new FlyExeThread(); //将线程放入池中进行执行 pool.execute(ft1); pool.execute(ft2); pool.execute(ft3); pool.execute(ft4); pool.execute(ft5); //关闭线程池 pool.shutdown(); } }
执行结果如下:
run: pool-1-thread-1执行中... pool-1-thread-1执行中... pool-1-thread-1执行中... pool-1-thread-1执行中... pool-1-thread-1执行中...
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。把上面的newSingleThreadExecutor()方法修改成:
ExecutorService pool = Executors.newFixedThreadPool(2);执行结果如下:
run: pool-1-thread-1执行中... pool-1-thread-2执行中... pool-1-thread-1执行中... pool-1-thread-2执行中... pool-1-thread-1执行中...3. newCachedThreadPool
Executors.newCachedThreadPool();
执行结果如下:
run: pool-1-thread-1执行中... pool-1-thread-4执行中... pool-1-thread-2执行中... pool-1-thread-5执行中... pool-1-thread-3执行中...4.newScheduledThreadPool
5.强烈建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程)它们均为大多数使用场景预定义了设置。
6.底层构造函数ThreadPoolExecutor的实现。
public ThreadPoolExecutor(int corePoolSize,//池中所保存的线程数,包括空闲线程。 int maximumPoolSize,//池中允许的最大线程数。 long keepAliveTime,//当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。 TimeUnit unit,//keepAliveTime参数的时间单位。 BlockingQueue<Runnable> workQueue,//执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。 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; }而上面几种ThreadExecutor的实现都是因为实现ThreadPoolExecutor的参数不同而有差异。例如fixedThreadPool是把corePoolSize的大小和maximumPoolSize的大小设置都设置成指定的大小。而cachedThreadPool是把corePoolSize的大小和maximumPoolSize的大小分别设置成0和Interger.MAX_VALUE,当然了它的keepAliveTime默认是一分钟。
当请求数目超过maximumPoolSize的话,请求时会被拒绝的,这也就是上面测试程序出现的情况,输入f的任务没有被执行。