九、线程池
首先,为什么要使用线程池?当我们写helloworld的时,简简单单创建一个线程处理简简单单的任务,那肯定不需要线程池。但是对于需要处理大量请求诸如web服务器这样的应用来说,如果每当一个请求到达就创建一个新线程,然后在新线程中为请求服务。那么,服务器在创建和销毁线程上花费的时间和消耗的系统资源要比花在处理实际的用户请求的时间和资源更多。除了创建和销毁线程的开销之外,活动的线程也消耗系统资源。在一个 JVM 里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目。线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。
在java.util.concurrent.Executors类中提供四种线程池:
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若缓存里没有可用的线程,则新建线程。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
示例一:
public class ThreadPoolTest { public static void main(String[] args) { //固定的线程池 ExecutorService executor= Executors.newFixedThreadPool(2); /** * 这里有五个Runnable任务,但是threadPool只有2个线程,所以每次只能执行两个任务。 */ for(int i = 1;i<=5;i++){ final int temp = i; threadPool.execute(new Runnable() { @Override public void run() { for(int j=1;j<=10;j++){ System.out.println(Thread.currentThread()+":i="+temp+"---j="+j); } } }); } threadPool.shutdown(); //当任务完成,结束线程 // threadPool.shutdownNow();//立即结束线程 } }
结果:
Thread[pool-1-thread-1,5,main]:i=1---j=1
Thread[pool-1-thread-1,5,main]:i=1---j=2
Thread[pool-1-thread-2,5,main]:i=2---j=1
.......
Thread[pool-1-thread-1,5,main]:i=1---j=9
Thread[pool-1-thread-1,5,main]:i=1---j=10
Thread[pool-1-thread-2,5,main]:i=2---j=4
Thread[pool-1-thread-1,5,main]:i=3---j=1
......
Thread[pool-1-thread-2,5,main]:i=2---j=8
Thread[pool-1-thread-1,5,main]:i=3---j=2
Thread[pool-1-thread-2,5,main]:i=2---j=9
Thread[pool-1-thread-2,5,main]:i=2---j=10
Thread[pool-1-thread-2,5,main]:i=4---j=1
......
上面的示例,executor循环地执行Runnable任务。但是无论循环几次,每次只有2两个线程处理其中的两个任务,只有一个任务处理完毕,才处理下一个任务,这就是newFixedThreadPool固定的线程池。
示例二:
public class ThreadPoolTest { public static void main(String[] args) { ExecutorService threadPool2 = Executors.newCachedThreadPool(); //当有Runnable任务,自动增加线程执行 // ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程,和Thread一样。线程池里面始终都有一个线程,而且,可能一直是同一个线程。而Thread创建的线程结束了就结束了 /** * 这里有五个Runnable任务,但是threadPool只有2个线程,所以每次只能执行两个任务。 * 而且,必须得线程池里面所有线程的任务都完成,才能执行其它任务。 */ for(int i = 1;i<=5;i++){ final int temp = i; threadPool.execute(new Runnable() { @Override public void run() { for(int j=1;j<=10;j++){ System.out.println(Thread.currentThread()+":i="+temp+"---j="+j); } } }); } threadPool.shutdown(); //当任务完成,结束线程 } }
打印结果:
Thread[pool-1-thread-1,5,main]:i=1---j=1
Thread[pool-1-thread-1,5,main]:i=1---j=2
Thread[pool-1-thread-2,5,main]:i=2---j=1
......
Thread[pool-1-thread-3,5,main]:i=2---j=10
Thread[pool-1-thread-4,5,main]:i=4---j=1
Thread[pool-1-thread-5,5,main]:i=5---j=1
......
与示例一不同是:newCachedThreadPool当线程不足时,会自动创建新的线程。
示例三:
public class ThreadPoolTest { public static void main(String[] args) { //定时器线程池 ScheduledExecutorService threadPool2 = Executors.newScheduledThreadPool(2); threadPool2.schedule(new Runnable() { @Override public void run() { System.out.println("hehe!"); } },2, TimeUnit.SECONDS); //TimeUnit单位,这里设定为秒,所以延迟2秒后执行 threadPool2.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("xixi"); } }, 5, 2, TimeUnit.SECONDS); //开始5秒之后执行一次,然后每隔2秒执行一次。 //要安排在某个以后的 Date 运行,可以使用:schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS) } }
关于 scheduleAtFixedRate()和scheduleWithFixedDealy()。
1、scheduleAtFixedRate 方法,顾名思义,它的方法名称的意思是:已固定的频率来执行某项计划(任务)。
2、scheduleWithFixedDealy,相对固定的延迟后,执行某项计划。
还是比较简单明了的描述比较好:第一个方法是固定的频率来执行某项计划,它不受计划执行时间的影响。到时间,它就执行。
而第二个方法,相对固定,据鄙人理解,是相对任务的。即无论某个任务执行多长时间,等执行完了,我再延迟指定的时间。也就是第二个方法,它受计划执行时间的影响。