关于线程池(以及多线程在哪里分叉)+匿名内部类+局部变量是否有final修饰的综合过程讲解

文章可能比较繁碎,但是这个例子的过程我花了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()里面的),即匿名内部类里面的内容,执行完就返回线程池变成空闲状态,换任务队列的下一个进程。

当子线程0 1 2执行完之后,回到线程池,接着执行main分叉出来的任务队列中的3个线程,即3  4  5线程,对应的index也分别为3, 4,5 ...后面6, 7, 8, 9也是一样。


再来一个不加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。

如有理解不对,恳请指正。


=============================我是一个反应迟钝的程序员==========================


猜你喜欢

转载自blog.csdn.net/qq_34115899/article/details/80012223
今日推荐