多线程
1. 进程的概念
* 我们都知道计算机的核心是CPU,他承担了所有的计算任务,而操作系统是计算机的管理者
操作系统负责任务的调度,资源的分配和管理
* 概念: 进程是一个具有独立功能的程序,在一个数据集上的一次动态的执行的过程
* 组成部分: 由三部分组成
程序: 用于描述进程要完成的功能
数据集合: 是程序在执行的过程中需要的资源
程序控制块 是进程存在的唯一的标志
* 特征:
动态性: 进程是程序的一次执行的过程,是动态产生和消亡的
并发性: 任何进程都可以与其他的进程一起并发执行
独立性: 进程是系统进行资源分配的的独立单位
结构性: 进程有陈序,数据和进程控制块三部分组成
2. 线程的发展史
1. 在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小的单位
任务的调度方式是: 分时间片抢占式调度
每个进程都有自己独立的一块内存,使各个进程之间相互隔离
2. 随着计算机的发展,对Cpu的要求越来越高,进程之间的切换开销比较大,无法满足负载的程序的要求了,于是就发明了线程
3. 概念: 线程是程序执行中一个单一的顺序控制流程, 是程序执行的最小的单元,是处理器(cpu)调度和分派的基本单位
4. 多线程的好处: 从根本上来讲是提高了CPU的利用率
- 线程和进程的区别
# 线程和进程的区别?
1. 根本区别:
进程是操作系统进行资源分配的基本单位 线程是任务调度和执行的基本单位,是程序执行的最小单位
2. 包含方面:
一个进程由一个或者多个线程组成
也就是单线程和多线程程序
3. 资源的开销:
进程都有独立的数据空间,进程之间切换开销系统资源比较大
同一程序下的多个线程共享程序的内存空间,切换开销的资源小
4. 所处的环境:
在操作系统中,同时可运行多个进程(程序)
而在同一进程中有多个线程同时执行(通过CPU的调度),实际上每个时间片中只有一个线程执行
5. 线程是轻量级的进程
3. 线程的创建方式
# 创建线程的四种方式
1. 继承Thread类
2. 实现Runnable接口
3. 实现Callable接口
4. 使用线程池
* 继承Threaad类创建线程的步骤
1. 定义一个类继承Threaad类
2. 重写run方法 (线程的真正的执行体)
3. 掉start() 方法开启线程
* 实现Runnable接口创建线程的步骤
1. 定义一个类实现Runnable接口
2. 实现run方法(线程的执行体)
3. 调用start()方法开启线程
* 实现Callable接口创建线程的步骤
1. 定义一个类实现Callable接口
2. 实现call方法(线程的执行体)
3. 调用start方法来开启线程
* 使用线程池 例如使用Executor 框架
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZbE7fG5J-1595148055816)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1573095927315.png)]
4. 线程的几种状态
# 线程的几种状态
1. 新建状态(NEW):
刚刚new出来的线程对象处于此状态
2. 运行状态(RUNNABLE):
调用start方法后,线程会进入运行的状态
3. 受阻塞(BLOCKED):
当线程执行到同步代码(同步代码块,同步方法),如果没有锁,会等着获取锁,此时线程计时受阻状态
4. 无限等待(WAITING):
当线程调用wait()后,会进入到无限等待状态(没有时间的等待,如果别人不叫醒我,那么我就一直等待)
5. 计时等待(TIMED_WAITING):
调用sleep(毫秒值),wait(毫秒值)会进入计时等待(有时间的等待转态,如果时间到了别人不叫我,我也会自己醒)
6. 终止状态(TERMINATED):
当线程执行完run方法,或者调用了stop方法会进入到终止的状态
5. 多线程的内存图
# 栈空间线程私有,堆空间线程共享
* 哪个线程执行都会开辟一块属于自己的栈空间,来运行自己的方法
* 方法是由哪个线程运行的那么就会在哪个线程的栈空间中开辟空间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HUqpuGiq-1595148055823)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1573099141639.png)]
6. 多线程引发的安全问题
# 多线程引发的安全的问题?
* 当多个线程同时操作一份资源 ,就有可能引发线程的安全的问题
* 当一个线程操作这个共同的资源的代码块时候,可能被其他的线程抢到cpu的执行权,因为线程的抢占线程的执行权是抢占式调度. 实际上线程的执行权还是由cpu决定的,cpu开心情随机给线程分配执行权
我们说到当一个线程操纵这个资源的时候,其他的线程也可能在操作这份资源 ,最终会导致资源错乱的现象
7. 如何解决线程引发的安全的问题
# 解决这种线程安全的问题?
1. 我们使用synchronized关键字来修饰代码块或者方法
它能保证在同一时间只有一个线程进入同步方法或者同步代码块
2. 使用lock锁
# synchronized关键字
1. 发展史:
在java的早期的版本synchronized是属于重量级的锁,效率低下;
java的线程是映射到98系统的原生系统上的
如果需要启动或者唤醒一个线程都需要依赖于操作系统来完成
操作系统实现线程之间的切换需要由用户态切换到内核态,切换的时间比价长,效率比较低下
JDK1.6对锁的实现引入了大量的优化,
如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销
8. 线程并发的三种的特性
# 线程并发的三种的特性:
1. 原子性
就是一个或者多个操作要么同时执行成功,要么同时执行失败
2. 可见性:
当多个线程操作同一份资源的时候,这个线程对这个资源的修改,要对其他的线程要可见
3. 有序性:
线程的执行的顺序是代码的先后的顺序执行的
9. 锁优化
* synchronized是重量级锁,效率不高。
但在jdk 1.6中对synchronize的实现进行了各种优化,使得它显得不是那么重了。
jdk1.6对锁的实现引入了大量的优化,
如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销
* 所主要的四种状态:
无锁转态,偏向锁转态,轻量级锁转态,重量级锁状态
他们会随之竞争的激烈程度二逐渐升级
锁可以升级但是不可以降级,这种策略是为了提高获得锁和释放锁的效率
10. 悲观锁和乐观锁
# 悲观锁和乐观锁
* 悲观锁:
顾明思义:就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次那数据的时候都会上锁,这样每次别人想拿到数据的时候都会锁上数据,直到别人拿到锁
传统的关系型数据库就用到了很多的这种锁机制
比如: 行锁,表锁,读锁,写锁
都是在操作之前先上锁
* 乐观锁:
顾明思义:就是很乐观,每次去拿数据的时候,认为别人不会去修改,所以不会上锁.
但是在更新的时候会判断一下在此期间别人有没有去更新这个数据.可以使用版本号等机制
乐观锁适用于多读的应用类型,这样可以提高吞吐量
像数据库提供类似于write_contidition机制就是提供了乐观锁
# 适用场景:
两种锁各有优缺点,不可以认为哪种好于另一种
乐观锁:适合写比较少,读比较多的场景,也就是冲突很少发生的场景,这样可以减少锁的开销,加大系统的吞吐量
如果经常发生冲突:使用悲观锁比较好
* 吞吐量:
系统在单位时间内处理请求的数量
12 Lock和synchronized的区别
1. 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2. Synic无法判断是否获取到锁的转态,Lock可以判断是否获取到锁
3. Sysnic可以自动释放锁,而Lock不会自动释放锁,会照成死锁的现象
4. Sysnic 的锁可重入,不可中断,非公平, 而Lock锁可重入,可判断,可公平
5. Sysnic锁适合小量的同步代码块的问题, 而Lock适合大量的代码同步的问题