对Java多线程、线程池以及在spring中的具体实现的一些浅见
- 一、理解多线程及并发问题需要的前置知识
- ★什么是线程,与进程的区别是什么
- ★JVM内存结构
- JVM内存结构
- 运行时数据区
- 上述各部分的作用
- ★什么是线程安全,什么是并发问题
- 二、线程的具体实现
- ★多线程的基本使用
- 线程的生命周期:
- 线程的创建方法
- ★线程池的概念及在spring中的实现
- 三、深入理解spring框架中的多线程与bean的关系
一、理解多线程及并发问题需要的前置知识
★什么是线程,与进程的区别是什么
一个操作系统中可以有多个进程,一个进程中可以有多个线程。
每个进程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存。
线程的执行是随机的。
单核处理器上线程是并发(CPU负责调度,分配时间片)执行的(假同时)。干一会儿A线程,再干一会儿B线程,因CPU执行速度非常快,给人类的感觉就是很多线程在同时干活。
现代CPU为多核,OS将线程作为最小调度单位,允许多线程并行运行(真同时)。
★JVM内存结构
JVM内存结构
可分为三大块:
1、类装载子系统;2、字节码执行引擎;3、运行时数据区;
运行时数据区
可分为五部分:
1、方法区(抽象概念)JDK8之前为永久代,之后为元空间;2、堆;3、虚拟机栈;4、本地方法栈;5、程序计数器;
线程运行过程中的数据在这五个部分是都有分布的,这里面的元空间、堆为所有线程所共享的,虚拟机栈、本地方法栈和程序计数器为线程私有。
上述各部分的作用
1、类装载子系统用于把.class文件加载到元空间;
2、字节码执行引擎用于执行代码,运行GC等;
3.1、元空间用于存放:常量、静态变量、类元信息;
3.2、堆用于存放:对象;
3.3、虚拟机栈用于给线程分配方法栈帧,里面具体又包括:局部变量表、操作数栈、动态链接、方法出口。
3.4、本地方法栈用于调用C++或其他底层语言的方法;
3.5、程序计数器用于记载线程执行到哪儿:线程可能被CPU暂停掉,并发地处理其他线程一会儿再回来,程序计数器的作用就是回来时能知道从哪行代码继续。类似于迅雷的断点续传。
★什么是线程安全,什么是并发问题
线程安全:多线程并发执行时,能保证数据的正确性。
线程不安全:多线程并发执行时,不能保证数据的正确性。
更具体的说,当多个线程共同争抢同一份数据时,就有可能出现并发问题。打个比方,多线程好比老板雇佣了很多员工帮他干活,每个线程就是一个员工,例如超市有很多收银员,可以并发地服务顾客并收钱。如果有两个或更多收银员为同一个顾客服务,就可能出现收两次或更多次钱的情况,顾客肯定不满意,要投诉这家超市了!
线程同步:不是同时的意思,是协同的意思,可称之为协同步调,排队依次执行。Synchronized是协同步调的意思。
原子性:要么一系列步骤都执行,要么都不执行。
二、线程的具体实现
★多线程的基本使用
线程的生命周期:
五种状态——
新建状态,new
可运行状态,start()方法执行后进入可运行状态
运行状态,CPU调度并执行该线程
阻塞状态, 等待阻塞wait(),
同步阻塞:获取synchronize同步锁失败,
其他阻塞(sleep()/join()/IO请求)
终止状态。线程执行完毕,或因异常退出run()方法。
线程的创建方法
继承Thread或实现Runnable接口。
步骤?创建一个继承了Thread的类,把要完成的业务放在重写的run()方法,
在main方法中new这个类的对象t,然后t.start();开启线程(从新建到可运行)
★线程池的概念及在spring中的实现
线程池是编程里面一种很重要的池化思想的具体实现。
举个例子,不用池的情况下,我们处理并发场景时,来一个任务,我们就创建一个线程,处理完这个任务就结束掉这个线程,这相当于我们打电话,每次都造一部电话出来,打一下,然后挂掉并把电话销毁。这样非常浪费资源,有大量的时间耗费在创建线程(造电话)和销毁线程(销毁电话)上面。
最好的方式是:在打电话之前准备好电话,需要的时候拿来用一下,用完把电话放回去,以后还能用。
Java处理并发量大的情况可以采用多线程的方式,在springboot主启动类里面准备好一个线程池,里面可以配置:
核心线程数、最大线程数、缓冲队列数、允许线程空闲时间、任务等待时间、线程池关闭策略、拒绝服务策略等等。
主启动类中配置bean交给spring管理:
@Bean(name = “changeThreadPoolTaskExecutor”)
public ThreadPoolTaskExecutor changeThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(6);// 核心线程数
executor.set…//一系列set
executor.initialize();
return executor;
}//干活的:
@Component(“asyncApiTask”)
@Async(“changeThreadPoolTaskExecutor”)
public class AsyncApiTask {
//@Autowired注入一些需要的bean
//方法用于干活
}
三、深入理解spring框架中的多线程与bean的关系
在spring框架中,我们知道注解存入容器的bean默认为单例模式,那么在多线程场景下,单例的bean会产生并发问题吗?这个问题困扰了我很久。
并不会产生并发问题,原因是:方法/函数并不是线程之间争抢的数据资源,而是可以被各线程共享和同时使用的,大家都可以用同样的方法/函数。
网友也有类似的疑问:多线程操作同一个对象并且调用相同的方法,为什么不会阻塞
belizer 发布于 2018/01/23 17:26 阅读 8K+ 收藏 4 答案 10
这个问题的缘起是spring mvc默认是单例的,所以我产生了这样一个疑问?在高并发下,是单例模式效率高还是多例模式效率高?之所以有这个疑问是因为我认为单例模式下,当一个请求在处理中的时候,接下来的请求会处于等待中(小白,大家勿笑)。之后有大神告诉我不会阻塞,因为web容器是多线程的。当时我觉得自己理解了。但之后我就咂摸这句话,是因为多线程所以不会阻塞。为什么多线程就不会阻塞呢?为什么多个线程操作同一个对象的同一个方法就不会阻塞呢?为什么多线程执行同一个对象的同一个方法中的相同代码不会阻塞呢?求教各位大神。
他城之途 2018/01/23 18:50
方法 存在JVM的一个内存区(即元空间),这个方法区(元空间)被各个线程共享。
打个不恰当的比方,如下:
老师在黑板上面写了一条1+1=?,同学们 同时看到信息后开始拿起笔计算。这里的黑板就相当于内存区,1+1=? 指令相当于方法,同学们相当于线程,大家一起并行计算,并没有阻塞。
那什么时候有阻塞?
老师说,谁要解答,请上讲台上来,这个时候可能会出现阻塞。