先来看下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 void createCachedThreadPool(){ 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(new Runnable() { 16 @Override 17 public void run() { 18 /* try { 19 //这里让启动的线程睡眠,保证下次启动的线程是新的线程,不是此时睡眠的。 20 Thread.sleep(index * 1000); 21 } catch (InterruptedException e) { 22 // TODO Auto-generated catch block 23 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 void createFixedThreadPool() { 5 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); 6 for (int i = 0; i < 10; i++) { 7 final int index = i; 8 fixedThreadPool.execute(new Runnable() { 9 @Override 10 public void run() { 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 void createScheduledThreadPoolToDelay(){ 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(new Runnable(){ 11 @Override 12 public void run() { 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 void createScheduledThreadPoolToFixRate(){ 2 ScheduledExecutorService exe = Executors.newScheduledThreadPool(3); 3 exe.scheduleAtFixedRate(new Runnable(){ 4 @Override 5 public void run() { 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 void createSingleThreadPool(){ 5 ExecutorService exe = Executors.newSingleThreadExecutor(); 6 for(int i = 0; i < 10; i++){ 7 final int index = i; 8 exe.execute(new Runnable() { 9 @Override 10 public void run() { 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(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
/** * Creates a <tt>LinkedBlockingQueue</tt> with a capacity of * {@link Integer#MAX_VALUE}. */ public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
应该尽量直接使用new ThreadPoolExecutor来创建线程池,并指定阻塞队列的容量。
参考文章:https://www.cnblogs.com/zhaoyan001/p/7049627.html