目录
2)解决线程安全问题的方案--- synchronized同步关键字
一、进程
1、进程的概念
进程就是正在运行的程序,它代表了程序所占用的内存区域。
2、进程的特点
1)独立性
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
2)动态性
进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的。
3)并发性
多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响。
二、串行、并行、并发
1、串行、并行
串行是大家排队一个一个来,并行是大家一起来。
2、并行、并发
HA(High Availability)高可用:指在高并发的情景中,尽可能的保证程序的可用性,减少系统不能提供服务的时间
3、并行与并发的关系
并行一定是并发,并发不一定是并行。
4、高并发
- 概念
多个进程抢占公共资源(例如售票案例、双十一)。
2.高并发有哪些指标
- QPS(Queries Per Second):每秒查询数
- 带宽
- PV(Page View)
- UV(Unique Visitor)
- 并发连接数
- 服务器平均请求等待时间
三、线程
- 线程是进程进行运算调度的最小单位
- 一个进程可以开启多个线程,一个进程也可以只包含一个线程--单线程程序
- 线程拓展了多进程,也就是一个进程可以干多件事情
- 每个线程拥有自己独立的内存空间,多个线程之间也存在共享数据
四、线程和进程有何异同
1、线程和进程的不同之处
1)起源不同
先有进程,后有线程(目的是为了提高执行效率)。
2)概念不同
- 进程:具有独立功能的、正在运行的一个程序(系统分配资源和调度的一个单位)。
- 线程:线程是进程(CPU)进行运算调度的最小单位。
3)内存共享方式不同
- 进程:不同的进程会被分配到不同的内存,但是不同进程之间的内存通常是不共享的(比如说浏览器、QQ、微信之间互不共享)。
- 线程:本身就服务于同一个进程,所以线程之间互相通信是十分容易的。
4)拥有的资源不同
- 线程共享的资源:
- 进程代码块
- 进程的共有数据(利用这些共享的数据,线程很容易的实现相互通信)
- 进程的打开的文件描述符
- 信号的处理器
- 进程的当前目录
- 进程用户ID与进程组ID
- 线程独有的内容包括:
- 线程ID
- 寄存器组的值
- 线程的堆栈
- 错误的返回码
- 线程的信号频闭码
5)数量不同
一个进程可以有多个线程(至少要有一个)。
6)开销不同
- 线程的创建、终止时间比进程短
- 同一进程内的线程切换时间比进程切换时间短
- 统一进程的各个线程时间共享内存和文件资源,可以不通过内核进行通信
2、线程和进程的相同之处
- 进程和线程都有ID/寄存器组、状态和优先权、信息块
- 创建后都可更改自己的属性
- 都可与父进程共享资源
- 都不直接访问其他无关进程或线程的资源
五、多线程
1、Java语言和多线程的渊源和关系
- Java设计之初就支持多线程,相比于当时的其他编程语言,十分具有优势。
- Java语言在服务器端开发语言中的地位--常年高居前三。
- 一对一映射到操作系统的内核线程。
- JVM会自动启动线程,即使代码中不显示创建线程,在运行main时,JVM也会自动启动其他的线程,测试如下:
- 测试代码:
- 打断点,debug模式运行,查看控制台信息,会发现,除了main,还自动启动了其他线程:
Signal Dispatcher:将操作系统发来的信号分发给适当的处理程序。
Finalizer:负责对象的finalize()方法,随着java语言的发展,不再推荐使用。
Rrference Handler:和GC(垃圾回收),引用相关的线程。
2、多线程的概念
- 宏观层面上:多个线程看起来是同时在运行。
- 微观层面上:一个CPU同一时刻只能处理一件事,至于哪个时刻执行哪个线程,取决于OS底层的算法(FCFS/SJS......)。
3、为什么需要多线程
- 最主要的目的就是提高CPU利用率
- 避免无效等待(IO的时候可以做别的事)
- 提高用户体验:避免卡顿、缩短等待时间
- 便于编程建模
4、线程执行的时机
OS选中它,并给它分配时间片(允许OS执行当前线程的一段时间);没有时间片的线程是挂起(暂停/冻结)的状态,等待再次被分配时间片才能运行。
5、线程的随机性
多个线程的执行效果是不可控的,因为CPU会调度处理,结果具有随机性;至于那个时间片执行哪一个线程,时间片有多长,我们都控制不了。
6、多线程的使用场景
为了同时做多件不同的事:比如说打开网页的同时听音乐、后台定时任务。
7、多线程的局限
- 性能问题:上下文切换带来的消耗。
- 异构化任务(任务结构不一样)很难高效并行。
- 带来的线程安全问题:包括数据安全问题(例如i++总数不一致)活跃性问题(线程饥饿、死锁)。
六、线程的几种状态以及状态之间的切换
1、线程的几种状态
- 创建状态:申请PCB,然后为该线程运行分配必须的资源(对应方法:new)。
- 就绪(可运行)状态:将该线程转为就绪状态插入到就绪队列中(对应方法:start())。
- 执行(运行)状态:线程被OS选中,分配时间片正常运行程序,时间片用完挂起加入到就绪队列当中,等待OS下一次选中。
- 阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞(锁阻塞/休眠10ms)。
- 终止状态:归还PCB,对应的空间清除。
2、三态模型
- 就绪 → 执行:为就绪线程分配CPU即可变为执行状态"。
- 执行 → 就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态。
- 执行 → 阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞。
- (例如线程正在访问临界资源,而资源正在被其他线程访问)。
- 反之,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行。
3、五态模型
七、创建多线程的三种方式
1、三种方式
- 继承Thread
- 实现Runnable接口
- 线程池:
- Executors :辅助创建线程池的工具类
- newFixedThreadPool(int nThreads) :指定线程池数n
- ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理
- pool.execute:(目标业务对象target) 把任务丢到线程池
2、Thread、Runnable两种实现方式的比较
- 继承Thread类:
- 优点: 编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
- 缺点: 自定义的线程类已继承了Thread类,所以后续无法再继承其他的类。
- 实现Runnable接口:
- 优点: 自定义的线程类只是实现了Runnable接口或Callable接口,后续还可以继承其他类,在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码、还有数据分开(解耦),形成清晰的模型,较好地体现了面向对象的思想。
- 缺点: 编程稍微复杂,如想访问当前线程,则需使用Thread.currentThread()方法。
八、多线程的安全问题
1、说明
- 多线程安全问题是如何出现的?
常见情况是由于线程的随机性+访问延迟。
- 如何判断程序有没有线程安全问题?
在多线程程序中 + 有共享数据 + 多条语句操作共享数据。
2、同步锁
1)异步与同步
- 同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。坏处就是效率会降低,不过保证了安全。
- 异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。坏处就是有安全隐患,但是效率要高一些。
2)解决线程安全问题的方案--- synchronized同步关键字
- 写法格式:
- 如果是实现接口的方式:
synchronized (锁对象){
需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
}
//锁对象:不限制类型,唯一就行,一般是Object o = new Object()
- 如果是实现类的方式:
synchronized (锁对象){ //
需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
}
//锁对象一般是 类名.class ,目的就是为了锁的唯一
2.同步效果的使用有两个前提:
- 同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)。
- 多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)。
九、关于多线程的常见面试题
- 进程和线程的相同和不同?
- 并行和并发的异同?
- 高并发是不是意味着多线程?有什么反例?
高并发是一种状态,多线程是一种解决方案,高并发并不意味着多线程。
比如:当前要解决数据库的高并发问题,很多请求同时到达数据库,数据库承受不了这样的压力,针对于这种情况,加一个redis缓存层。这种情况下,会优先访问redis,redis会将大量的压力承受下来,减轻数据库的压力。高并发并不意味着多线程。 redis吞吐量大,高并发,却是单线程。
4.多线程可以提高程序执行效率,你知不知道有哪些弊端?