zoukankan      html  css  js  c++  java
  • 关于线程池(以及多线程在哪里分叉)+匿名内部类+局部变量是否有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。

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


    ========================================Talk is cheap, show me the code=======================================


    CSDN博客地址:https://blog.csdn.net/qq_34115899
  • 相关阅读:
    正则表达式匹配整数和小数
    解决任务计划程序未启动任务,因为相同任务的实例正在运行的问题
    ActiveMQ 消息持久化到数据库(Mysql、SQL Server、Oracle、DB2等)
    C# CLR20R3 程序终止的几种解决方案
    彻底消除wine中文乱码,QQ,kugoo等等....
    Fedora如何添加第三方软件源?
    [转]Fedora 下安装NVIDIA显卡驱动(使用后无法进入图形界面)
    向fedora添加rpmfusion源
    [转]Java 8 Optional类深度解析(null处理)
    [转载]深入理解Java 8 Lambda
  • 原文地址:https://www.cnblogs.com/lcy0515/p/9179761.html
Copyright © 2011-2022 走看看