当前位置:网站首页>01 多线程与高并发 - 基础概念
01 多线程与高并发 - 基础概念
2022-07-30 06:10:00 【小刘说】
线程历史
- 单进程人工切换
纸带机 - 多进程批处理
多个任务批量执行 - 多进程并行处理
把程序写在不同的内存位置上来回切换 - 多线程
一个程序内部不同任务来回切换
基本概念
进程
系统分配资源(独立的地址空间、内核数据结构、全局变量等)的基本单元线程
普通的进程,和其他进程共享资源,一个进程中的不同执行路线纤程(Fiber)
用户态的线程,线程中的线程,切换和调度都不需要经过OS
(1) 优点
1.占有资源很少 OS启动一个线程 1M ,启动一个纤程 4K
2.切换比较简单
3.可以启动很多个
(2) 应用场景
很短的计算任务,不需要和内核打交道,并发量高内核线程
内核启动之后经常需要做一些后台操作,这些由内核线程来完成,只在内核空间运行进程的创建启动
从进程A中fock进程B的话,A为B的父进程,系统函数fork()->创建,exec()->启动僵尸进程 – defunct
父进程产生子进程后,会维护子进程的一个PCB(存放进程的管理和控制信息的数据结构)结构,子进程退出,有父进程释放,如果父进程没有释放则子进程成为僵尸进程孤儿进程
子进程结束之前,父进程已经退出,孤儿进程会成为一个特殊进程(一般为1号进程)的孩子,由此特殊进程维护进程和线程有什么区别
答案:进程就是一个程序运行起来的状态,线程是一个进程中的不同的执行路径。
专业:进程是os分配资源的基本单位,线程是执行调度的基本单位。分配资源最重要的是:独立的内存空间,线程调度执行(线程共享进程的内存空间,没有自己独立的内存空间)线程和纤程区别
纤程在用户态,jvm自己管理自己切换,多个纤程对应一个线程进程调度
(1) 进程类型IO 密集型– 大部分时间用于等待IOCPU 密集型– 大部分用于计算
(2)进程优先级
实时进程 > 普通进程
(3) 多任务非抢占式– 除非进程主动让出CPU,否则将一直运行抢占式– 由进程调度器强制开始或暂停(抢占)某一进程的执行调度策略
实时进程 -> 优先级按高低区分用 FIFO (First In First Out),优先级一样用 RR (Round Robin)
普通进程 -> CFS (Completely Fair Scheduler, 完全公平调度算法),按优先级分配时间片的比例,记录每个进程的执行时间,如果有一个进程执行时间不到它分配的比例,优先执行。其中等级最高的是FIFO,这种进程除非自己让出CPU或更高等级的FIFO和RR抢占它否则会一直执行,RR只是线程中同级别FIFO中的平均分配。只有实时进程主动让出,或者执行完毕后,普通进程才有机会执行
中断(信号)
- 硬中断
硬件跟操作系统内核打交道的一种机制 - 软中断
系统中断(80中断),int 0x80 或者 sysenter 原语
- 硬中断
线程数量
不是越大越好,线程上下文切换需要消耗系统资源
num = N(cpu) * U(cpu) * (1 + W/C)
N:处理器的核数
U:期望CPU的利用率
W/C:等待时间与计算时间的比率
常见线程方法
- sleep()
sleep是让当前线程进入休眠状态,让出CPU资源,但是不会释放锁
1. wait必须配合synchronized使用,而sleep不用;
2. wait属于Object(对象)的方法,而sleep属于Thread(线程)的方法;
3. sleep不释放锁,而wait要释放锁;
4. sleep必须要传递一个数值型的参数,而wait可以不传参;
5. sleep让线程进入到TIMED_WAITING状态,而无参的wait方法让线程进入了WAITING状态;
6. 一般情况下,sleep只能等待超过时间之后才能恢复执行,而wait可以接收notify、notifyAll之后就可以执行。
yield()
当前线程正在执行的时候停止下来进入等待队列(就绪状态,CPU依然有可能把这个线程拿出来运行),回到等待队列里在系统的调度算法里还是依然有可能把你刚回去的这个线程拿回来继续执行,当然,更大的可能性是把原来等待的那些拿出一个来执行,所以yield的意思是我让出一下CPU,后面你们能不能抢到那我不管join()
当前线程加入你调用Join的线程,本线程等待,等调用的线程运行完了,自己再去执行interrupt()
线程的"打断",设置标志位,并不是打断线程的运行
- **interrupt**
* public void interrupt()
* t.interrupt() 打断t线程(设置t线程某给标志位f=true,并不是打断线程的运行)
- **isInterrupted**
* public boolean isInterrupted()
* t.isInterrupted() 查询打断标志位是否被设置(是不是曾经被打断过)
- **interrupted**
* public static boolean interrupted()
* Thread.interrupted() 查看“当前”线程是否被打断,如果被打断,恢复标志位
线程状态

- NEW : 线程刚刚创建,还没有启动
- RUNNABLE : 可运行状态,由线程调度器可以安排执行,包括READY和RUNNING两种细分状态
- WAITING: 等待被唤醒
- TIMED WAITING: 隔一段时间后自动唤醒
- BLOCKED: 被阻塞,正在等待锁
- TERMINATED: 线程结束
如何结束一个线程
- 自然结束(能自然结束就尽量自然结束)
- stop() suspend() resume()
弃用,可能照成数据不一致的问题 - volatile 标志
- 不适合某些场景(比如还没有同步的时候,线程做了阻塞操作,没有办法循环回去)
- 打断时间也不是特别精确,比如一个阻塞容器,容量为5的时候结束生产者,
但是,由于volatile同步线程标志位的时间控制不是很精确,有可能生产者还继续生产一段儿时间
- interrupt() and isInterrupted(比较优雅)
并发编程三大特性
可见性
所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成
要想保证可见,要么触发同步指令,要么加上volatile,volatile 的可见性是指当多个线程访问同一个变量(共享变量)时,如果在这期间有某个线程修改了该共享变量的值,那么其他线程能够立即看得到修改后的值。使用volatile,将会强制所有线程都去堆内存中读取running的值
有序性
因为CPU的指令重排,可能会导致多线程会产生不希望看到的结果,有些场景需要禁止乱序,保证有序性。
禁止乱序:
(1) CPU层面
内存屏障,对某部分内存做操作时中间添加屏障,屏障前后的操作不可乱序执行
(2) JVM 层级
8个 hanppens-before 原则,4个内存屏障(LL LS SL SS)
- Load屏障,是x86上的”ifence“指令,在其他指令前插入ifence指令,
可以让高速缓存中的数据失效,强制当前线程从主内存里面加载数据 - Store屏障,是x86的”sfence“指令,在其他指令后插入sfence指令,
能让当前线程写入高速缓存中的最新数据,写入主内存,让其他线程可见
volatile 底层使用了 LOCK 指令
inline void OrderAccess::fence() {
if (os::is_MP()) {
// always use locked addl since mfence is sometimes expensive
#ifdef AMD64
__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
}
}
LOCK 用于在
多处理器中执行指令时对共享内存的独占使用。
它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效。
另外还提供了有序的指令无法越过这个内存屏障的作用。
原子性
原子操作的不可分割有两层含义:
- 访问(读、写)某个共享变量的操作从其他线程来看,该操作要么已经执行要么尚未发生,即其他线程看不到当前操作中的中间结果
- 访问同一组共享变量的原子操作时不能相交错的。如现实生活中从ATM机取款。
保证原子性可以加锁,JVM中的两种锁:
- 重量级锁(经过操作系统的调度)synchronized早期都是这种锁(目前的实现中升级到最后也是这种锁)
- 轻量级锁(CAS的实现,不经过OS调度)(无锁 - 自旋锁 - 乐观锁)
边栏推荐
- go : go-redis set operations
- C# 获取系统已安装的.NET版本
- [GO Language Basics] 1. Why do I want to learn Golang and get started with GO language
- How to use Swagger, say goodbye to postman
- Go 使用 freecache 缓存
- MySQL题外篇【ORM思想解析】
- [GO语言基础] 一.为什么我要学习Golang以及GO语言入门普及
- MYSQL 主从恢复锁表后, 处理SQL 线程锁解决.
- redis实现分布式锁的原理
- 分布式系统中的开创者—莱斯利·兰伯特
猜你喜欢

Boot process and service control

BGP:边界网关路由协议 无类别的路径矢量EGP协议

What happens when @Bean and @Component are used on the same class?

Go 结合Gin导出Mysql数据到Excel表格

Ali Ermian: How many cluster solutions does Redis have?I answered 4

预测人们对你的第一印象,“AI颜狗”的诞生

Ali two sides: List several tips for Api interface optimization

Electron之初出茅庐——搭建环境并运行第一个程序

What new materials are used in the large aircraft C919?

Ali two sides: Sentinel vs Hystrix comparison, how to choose?
随机推荐
树状数组的基本用法
五号黯区靶场 mysql 注入之limit注入记录
go : go-redis set operations
interface
Develop common tool software
Go uses freecache for caching
和AI一起玩儿剧本杀:居然比我还入戏
assert
Redis 如何实现防止超卖和库存扣减操作?
2020 ACM | MoFlow: An Invertible Flow Model for Generating Molecular Graphs
LSF提交作业命令--bsub
[GO Language Basics] 1. Why do I want to learn Golang and get started with GO language
从追赶到超越,国产软件大显身手
MYSQL下载及安装完整教程
Electron之初出茅庐——搭建环境并运行第一个程序
export , export default, import complete usage
How to understand plucker coordinates (geometric understanding)
go : create database records using gorm
【day5】数组
mysql高阶语句(一)