文章可能比较繁碎,但是这个例子的过程我花了2天才想清楚。涉及的点较多,初次理解或许较难。
线程池在系统启动的时即创建大量空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个线程来执行它们的run()或call()方法,当run()或call()方法执行结束后,该线程不会死亡,而是返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法。
除此之外,使用线程池可以有效的控制系统中并发线程的数量,当系统中包含大量并发线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃,而线程池最大线程数可以控制系统中并发线程数不会超过此数。
Executors.newFixedThreadPool内部有个任务队列,如果Executors.newFixedThreadPool(3); 则线程池里有3个线程,提交了5个任务,那么后两个任务就放在任务队列了,即使前3个任务sleep或者堵塞了,也不会执行后两个任务,除非前三个任务有执行完的,就空闲出来,再执行下一个进程。
先来一个加了final的例子
(匿名内部类访问局部变量为什么必须用final修饰???
因为当调用这个方法时,局部变量如果没有用final修饰,他的生命周期和方法的生命周期是一样的,当方法弹栈,这个局部变量也会消失,那么如果局部内部类对象还没有马上消失想用这个局部变量,就没有了,如果用final修饰会在类加载的时候进入常量池,即使方法弹栈,常量池的常量还在,也可以继续使用)
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExecutorTest { public static void main(String[] args) { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); System.out.println("外面到底几次"); for (int i = 0; i < 10; i++) { final int index = i; System.out.println("第" + index + "个进程"); fixedThreadPool.execute(new Runnable() { public void run() { try { System.out.println(index); Thread.sleep(2000); System.out.println(index+" 睡眠后再次打印"); } catch (InterruptedException e) { e.printStackTrace(); } } }); System.out.println("i:" + i); } } }
运行结果:(截图原因可能会有很长的换行,请忽略)
主线程main执行到fixedThreadPool.execute的时候,子线程开始了执行。执行run()方法里面的内容,子线程不会执行到匿名内部类之外的东西。与此同时,main线程继续往下执行语句,然后开始下一轮循环,可以测试出,
如果在fixedThreadPool.execute(new Runnable(){...})之外的时候
打印线程名System.out.println("线程名:" + Thread.currentThread().getName());
都会发现是线程名:main,只有在匿名内部类里面测试才是pool-1-thread-1, pool-1-thread-2, pool-1-thread-3
所以,在外面执行循环的都是main线程。
main线程都循环完了,创建了10个子进程,而只能有3个子进程同时执行。
main进程每次循环都会分叉出一个新的子进程。
这里newFixedThreadPool的任务队列设置为了3,所以只能容纳3个子线程同时执行。
在这个过程中要打印index,index是在常量池,是JVM的一块特殊内存,i是栈内存,当把i的值赋给index时,值就存在于常量池,打印index的时候,main进程循环做的很快,即使循环可能已经执行了好几次了,i虽然已经变化,index=i,但是每个线程都拥有一个index常量,循环了几次,有几个线程,就有几个index常量,都有着各自的内存。就像不同的对象的方法都有自己的局部变量j一样,名字相同但是互不影响。
不加final,index会被反复赋值,index和i都是栈内存,加了final,只会被赋值一次,就是那一层循环的i值,也就是第i个线程。index是在常量池内存,i在占内存,i会在循环结束后回收,但是index不会。
当2s睡眠之间结束,子线程就开始执行run()后面的往下的内容(只会执行run()里面的),即匿名内部类里面的内容,执行完就返回线程池变成空闲状态,换任务队列的下一个进程。
再来一个不加final的例子
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExecutorTest { public static int index; // 设为全局变量 public static void main(String[] args) { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); System.out.println("外面到底几次"); for (int i = 0; i < 10; i++) { index = i; System.out.println("第" + index + "个进程"); fixedThreadPool.execute(new Runnable() { public void run() { try { System.out.println(index); Thread.sleep(2000); System.out.println(index+" 睡眠后再次打印"); } catch (InterruptedException e) { e.printStackTrace(); } } }); System.out.println("i:" + i); } } }
运行结果:
在这个过程中要打印index,index和i是2块栈内存,栈内存里面的值是相等的,每次分叉出新线程时就会进入下一次循环,就会进行index=i的赋值语句,但是当打印index的时候,main线程循环做的很快,循环可能已经执行了好几次了,i已经变化,打印出的index就不是从0开始,而是此时i的值,于是多运行几次就出现的上图的不确定情况。
当2s睡眠之间结束,子线程就开始执行run()后面的往下的内容(只会执行run()里面的),即匿名内部类里面的内容,执行完就返回线程池变成空闲状态,换任务队列的下一个进程。
等睡眠时间结束i已经是10并且循环结束已经销毁了,最后一次index保存的i值是9,所以子线程依次往下都会打印出9。
如有理解不对,恳请指正。
========================================Talk is cheap, show me the code=======================================