先来看下ThreadPool的类结构
其中红色框住的是常用的接口和类(图片来自:https://blog.csdn.net/panweiwei1994/article/details/78617117?from=singlemessage)
为什么需要线程池呢?
我们在创建线程的时候,一般使用new Thread(),但是每次在启动一个线程的时候就new 一个Thread对象,会让性能变差(spring不都使用IOC管理对象了嘛)。还有其他的一些弊端:
可能会造成无限创建线程对象,对象之间相互竞争资源,造成过多占用资源而宕机。
缺乏相关功能,如定时执行、定期执行、线程中断。
使用线程池的避免这些事情:
重用存在的线程,减少对象创建、消亡的开销,性能佳。
可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
提供定时执行、定期执行、单线程、并发数控制等功能。
线程池的种类:
java通过Executor是提供4种线程池,分别为:
1)newCachedThreadPool:创建一个可缓存的线程池,有任务来临时如果线程池中有空闲的线程,那么就使用空闲的线程执行任务(即线程是可以复用),如果没有空闲线程则创建新的线程执行任务。
2)newFixedThreadPool:创建一个定长的线程池,线程池的线程数量固定,当任务来临,但是又没有空闲线程,则把任务放入队列中等待直到有空闲线程来处理它。
3)newScheduledThreadPool:创建一个定长的线程,但是能支持定时或周期性的执行。
4)newSingleThreadPool:创建一个单线程化的线程池,线程池中只有一个唯一的线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。
示例:
1)newCachedThreadPool
1 /**
2 * 创建可缓存的线程池,线程池的线程可以重复利用,除非任务来不及处理就会创建新的线程。3 */
4 public static voidcreateCachedThreadPool(){5 ExecutorService cachedThreadPool =Executors.newCachedThreadPool();6 for (int i = 0; i < 10; i++) {7 final int index =i;8 try{9 //在这里使主线程停下来,让启动的线程执行完Syso操作10 //并且有时间回收线程以确保下次启动的线程还是上次的线程
11 Thread.sleep(index * 1000);//
12 } catch(InterruptedException e) {13 e.printStackTrace();14 }15 cachedThreadPool.execute(newRunnable() {16 @Override17 public voidrun() {18 /*try {19 //这里让启动的线程睡眠,保证下次启动的线程是新的线程,不是此时睡眠的。20 Thread.sleep(index * 1000);21 } catch (InterruptedException e) {22 // TODO Auto-generated catch block23 e.printStackTrace();24 }*/
25 System.out.println(Thread.currentThread().getName() + " " +index);26 }27 });28
29 }30 }
执行结果:
结果显示,执行for循环输出的线程都是同一个,线程重复使用了。
把注释的地方放开,并且注释上面的睡眠,执行结果:
结果显示的是不同的线程名称执行的for循环,对比上面的执行结果的线程名称,可以得出结论:有任务来临时如果线程池中有空闲的线程,那么就使用空闲的线程执行任务(即线程是可以复用),如果没有空闲线程则创建新的线程执行任务。
2)newFixedThreadPool
1 /**
2 * 创建固定长度的线程池,超出的任务会在队列中进行等待,直到有线程空出来来执行。3 */
4 public static voidcreateFixedThreadPool() {5 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);6 for (int i = 0; i < 10; i++) {7 final int index =i;8 fixedThreadPool.execute(newRunnable() {9 @Override10 public voidrun() {11 try{12 System.out.println(Thread.currentThread().getName() + "," +index);13 Thread.sleep(2000);14 } catch(InterruptedException e) {15 e.printStackTrace();16 }17 }18 });19 }20 }
运行结果:
创建了固定长度是3的线程池,输出前3行之后,发现线程都在sleep(),要执行的输出任务没有找到对应的执行线程,任务就会放入队列中进行等待,等待某个线程执行完毕后,再去执行任务。(线程池中的线程也是重复使用的)
3)newScheduledThreadPool
3.1)延迟执行某个线程
1 public static voidcreateScheduledThreadPoolToDelay(){2 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);3 for(int i = 0; i < 10; i++){4 final int index =i;5 try{6 Thread.sleep(2000);7 } catch(InterruptedException e) {8 e.printStackTrace();9 }10 scheduledThreadPool.schedule(newRunnable(){11 @Override12 public voidrun() {13 System.out.println(Thread.currentThread().getName() + " " + index + " delay 3 seconds");14 }15 }, 3, TimeUnit.SECONDS);16 }17 }
执行结果:
延迟3+2秒执行(3秒是newScheduledThreadPool中设置的,2秒是Thread.sleep()设置的),结果中可以看出主线程睡眠2秒并不能保证newScheduledThreadPool线程池中是使用旧线程执行任务还是新建线程执行任务,这种情况是随机的。(这点和newCachedThreadPool不一样,newCachedThreadPool是用就线程)
3.2)定期执行某个任务
1 public static voidcreateScheduledThreadPoolToFixRate(){2 ScheduledExecutorService exe = Executors.newScheduledThreadPool(3);3 exe.scheduleAtFixedRate(newRunnable(){4 @Override5 public voidrun() {6 System.out.println(Thread.currentThread().getName() + " delay 1 seconds, and excute every 3 seconds");7 }8 }, 1, 3, TimeUnit.SECONDS);9
10 }
执行结果:
结果是延迟1s启动线程,并且之后每隔3s重复执行任务,但是用的是同一个线程。
4)newSingleThreadPool
1 /**
2 * 创建一个单线程的线程池3 */
4 public static voidcreateSingleThreadPool(){5 ExecutorService exe =Executors.newSingleThreadExecutor();6 for(int i = 0; i < 10; i++){7 final int index =i;8 exe.execute(newRunnable() {9 @Override10 public voidrun() {11 try{12 System.out.println(Thread.currentThread().getName() + ", " +index);13 Thread.sleep(2000);//让当前线程睡眠2s,发现顺序打印1~10,并且有个打印停顿2s
14 } catch(InterruptedException e) {15 e.printStackTrace();16 }17 }18 });19 }20 }
运行结果:
每输出一行结果就等待2s,可以看出每次输出的线程名都一样,说使用的同一个线程。单线程化的线程池中只有一个线程。
总结:
上面就是4中线程池的实现及其使用示例,和他们之间的区别。
其中newFixedThreadPool()有个坑,最好不要使用Executor.newFixedThreadPool(int nThreads)来创建线程池,因为它使用了LinkedBlockingQueue,容量是Integer.MAX_VALUE,容量太大容易造成防止所有任务都被阻塞,从而导致死锁。下面是具体源码:
public static ExecutorService newFixedThreadPool(intnThreads) {return newThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());
}
/*** Creates a LinkedBlockingQueue with a capacity of
* {@linkInteger#MAX_VALUE}.*/
publicLinkedBlockingQueue() {this(Integer.MAX_VALUE);
}
应该尽量直接使用new ThreadPoolExecutor来创建线程池,并指定阻塞队列的容量。
参考文章:https://www.cnblogs.com/zhaoyan001/p/7049627.html