线程安全的本质
文章目录
线程和进程的区别
作者:peonyX 链接:https://www.nowcoder.com/discuss/723383?source_id=profile_create_nctrack&channel=-1 来源:牛客网
进程和线程的区别:进程是调度资源的基本单位,线程是执行任务的基本单位。进程有自己的独立数据空间,程序切换的开销大,线程共享一个进程的数据空间,每个线程有自己独立的运行栈和程序计数器,线程之间开销小。进程之间的资源隔离,共享复杂,线程的共享资源简单。线程的目的是为了并发,因为线程上下文的切换快,可以提高并发效率。
进程间通信方式,6种-重点
1.管道:单向的,所以需要两条管道;只能用于父子进程,兄弟进程(亲缘关系);数据先进先出;缓冲区是一个循环队列,满了之后会扔到阻塞队列里面 2.有名管道:不需要必须父子进程,兄弟进程;也是先进先出 3.信号:比如kill -9会发送SIGKILL信号 4.消息队列:消息队列在内存中,允许多个进程写入或者读取;也是先进先出 5.共享内存:多个进程可以读写同一块空间 6.socket
孤儿进程和僵尸进程
孤儿进程表示父进程退出了,但是子进程还在运行,这些就是孤儿进程,孤儿进程会被init进程(进程号为1)回收,所以基本不会有问题。僵尸进程是子进程正常退出,会给父进程发送信号,告诉父进程释放掉进程号,但是如果父进程不选择接受,不调用wait/waitpid,进程号将不会被释放。https://www.cnblogs.com/anker/p/3271773.html
为什么线程切换快
最主要的一个区别在于进程切换涉及虚拟地址空间的切换而线程不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。 有的同学可能还是不太明白,为什么虚拟地址空间切换会比较耗时呢? 现在我们已经知道了进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,页表查找是一个很慢的过程,因此通常使用Cache来缓存常用的地址映射,这样可以加速页表查找,这个cache就是TLB(translation Lookaside Buffer,我们不需要关心这个名字只需要知道TLB本质上就是一个cache,是用来加速页表查找的)。由于每个进程都有自己的虚拟地址空间,那么显然每个进程都有自己的页表,那么当进程切换后页表也要进行切换,页表切换后TLB就失效了,cache失效导致命中率降低,那么虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢,而线程切换则不会导致TLB失效,因为线程线程无需切换地址空间,因此我们通常说线程切换要比较进程切换块,原因就在这里。
作者:peonyX 链接:https://www.nowcoder.com/discuss/723383?source_id=profile_create_nctrack&channel=-1 来源:牛客网
内核态和用户态的区别
内核态可以访问所有数据,包括硬盘,网卡,也可以切换程序。用户态只能访问受限的内存,不能访问外围设备,占用CPU的能力被剥夺。
进程调度 算法
先进先出,最短耗时优先,时间片轮转,多级反馈队列。https://www.jianshu.com/p/ecfddbc0af2d
1、时间片轮转调度 算法 (RR):给每个进程固定的执行时间,根据进程到达的先后顺序让进程在单位时间片内执行,执行完成后便调度下一个进程执行,时间片轮转调度不考虑进程等待时间和执行时间,属于抢占式调度。优点是兼顾长短作业;缺点是平均等待时间较长,上下文切换较费时。适用于分时系统。 2、先来先服务调度算法(FCFS):根据进程到达的先后顺序执行进程,不考虑等待时间和执行时间,会产生饥饿现象。属于非抢占式调度,优点是公平,实现简单;缺点是不利于短作业。 3、优先级调度算法(HPF):在进程等待队列中选择优先级最高的来执行。 4、多级反馈队列调度算法:将时间片轮转与优先级调度相结合,把进程按优先级分成不同的队列,先按优先级调度,优先级相同的,按时间片轮转。优点是兼顾长短作业,有较好的响应时间,可行性强,适用于各种作业环境。 5、高响应比优先调度算法:根据“响应比=(进程执行时间+进程等待时间)/ 进程执行时间”这个公式得到的响应比来进行调度。高响应比优先算法在等待时间相同的情况下,作业执行的时间越短,响应比越高,满足段任务优先,同时响应比会随着等待时间增加而变大,优先级会提高,能够避免饥饿现象。优点是兼顾长短作业,缺点是计算响应比开销大,适用于批处理系统。
作者:韩故 链接:https://www.jianshu.com/p/ecfddbc0af2d 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
虚拟内存
虚拟内存 使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上。每个进程有独立的地址空间,这个空间有许多页,这些页不用所有都在内存中才能运行程序,所以一部分存在磁盘中,需要的时候再映射
其他
程序里大部分语句都要访问内存,有些还要访问 I/O,根据木桶理论(一只水桶能装多少水取决于它最短的那块木板),程序整体的性能取决于最慢的操作——读写 I/O 设备,也就是说单方面提高 CPU 性能是无效的。
为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系机构、操作系统、编译程序都做出了贡献,主要体现为:
- CPU 增加了缓存,以均衡与内存的速度差异;
- 操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;
- 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。
线程安全问题的本质
每个线程都有自己的副本编程【CPU 缓存】, 修改多线程修改变量 是修改自己的缓存,然后再将缓存写回主存,这样就 可能导致多线程 变量副本的不一致性
源头之三:编译优化带来的有序性问题
那并发编程里还有没有其他有违直觉容易导致诡异 Bug 的技术呢?有的,就是有序性。顾名思义,有序性指的是程序按照代码的先后顺序执行。编译器为了优化性能,有时候会改变程序中语句的先后顺序,例如程序中:“a=6;b=7;”编译器优化后可能变成“b=7;a=6;”,在这个例子中,编译器调整了语句的顺序,但是不影响程序的最终结果。不过有时候编译器及解释器的优化可能导致意想不到的 Bug。
在 Java 领域一个经典的案例就是利用双重检查创建单例对象,例如下面的代码:在获取实例 getInstance() 的方法中,我们首先判断 instance 是否为空,如果为空,则锁定 Singleton.class 并再次检查 instance 是否为空,如果还为空则创建 Singleton 的一个实例。
|
|
long类型64位,所以在32位的机器上,对long类型的数据操作通常需要多条指令组合出来,无法保证原子性,所以并发的时候会出问题🌝
线程的几种运行状态
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
- 阻塞(BLOCKED):表示线程阻塞于锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
- 终止(TERMINATED):表示该线程已经执行完毕。
java synchronized 关键字原理
管程
Synchronized 关键字 其实是 管程【monitor】 实现的, monitor enter 和 monitor exit,用汇编来说
为什么 Java 在 1.5 之前仅仅提供了 synchronized 关键字及 wait()、notify()、notifyAll() 这三个看似从天而降的方法?在刚接触 Java 的时候,我以为它会提供信号量这种编程原语,因为操作系统原理课程告诉我,用信号量能解决所有并发问题,结果我发现不是。后来我找到了原因:Java 采用的是管程技术,synchronized 关键字及 wait()、notify()、notifyAll() 这三个方法都是管程的组成部分。而管程和信号量是等价的,所谓等价指的是用管程能够实现信号量,也能用信号量实现管程。但是管程更容易使用,所以 Java 选择了管程。
MESA 模型
在管程的发展史上,先后出现过三种不同的管程模型,分别是:Hasen 模型、Hoare 模型和 MESA 模型。其中,现在广泛应用的是 MESA 模型,并且 Java 管程的实现参考的也是 MESA 模型。所以今天我们重点介绍一下 MESA 模型。
管程解决互斥问题的思路很简单,就是将共享变量及其对共享变量的操作统一封装起来。在下图中,管程 X 将共享变量 queue 这个队列和相关的操作入队 enq()、出队 deq() 都封装起来了;线程 A 和线程 B 如果想访问共享变量 queue,只能通过调用管程提供的 enq()、deq() 方法来实现;enq()、deq() 保证互斥性,只允许一个线程进入管程。不知你有没有发现,管程模型和面向对象高度契合的。估计这也是 Java 选择管程的原因吧。而我在前面章节介绍的互斥锁用法,其背后的模型其实就是它。
epoll两种工作模式
水平触发(没写完不断通知),边缘触发(只通知一次),LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读 => https://www.jianshu.com/p/73e9ef7902e1
文章作者 LYR
上次更新 2021-08-17