进程,线程,协程、MESI

进程,线程,协程的概念
1、进程: 操作系统进行资源分配和调度的基本单位。每个进程有独立的内存空间。进程通讯就采用共享内存,MQ,管道。
2、线程: 一个进程可以包含多个线程,线程就是CPU调度的基本单位。一个线程只属于某一个进
程。线程之间通讯,队列,await,signal,wait,notity,Exchanger,共享变量等等都可以实现
线程之间的通讯。
3、协程:
协程是一种用户态的轻量级线程。它是由程序员自行控制调度的。可以显示式的进行切换。
一个线程可以调度多个协程。
协程只存在于用户态,不存在线程中的用户态和内核态切换的问题。协程的挂起就好像线程的
yield,可以基于协程避免使用锁这种机制来保证线程安全。
协程和线程两者差异对比:
更轻量: 线程一般占用的内存大小是MB级别,但协程占用的内存大小是KB级别。
简化并发问题: 协程咱们可以自己控制异步编程的执行顺序,协程就类似是串行的效果。
减少上下文切换带来的性能损耗: 协程是用户态的,不存在线程挂起时用户态和内核态的切
换,也不需要去让CPU记录切换点。
协程优点: 协程在针对大量的IO密集操作时,协程可以更好有去优化这种业务。
创建线程方式
Java中创建线程方式,只有一种,本质都是Runnable的形式。
1、继承Thread
2、实现Runnable
3、Callable:Callable一般需要配合FutureTask来执行,执行的是FutureTask中的run方法,而
FutureTask实现了RunnableFuture的接口,RunnableFuture的接口又继承的Runnable。
4、线程池:线程池中的工作线程是Worker,Worker实现了Runnable,在构建工作线程时,会
new Worker对象,将Worker传递给线程工厂构建的Thread对象。
说明:根据底层源码来看,本质上都是实现Runnable
线程结束怎样?
1.结束线程比较优雅的方式其实只有一个, run方法结束 (正常或者异常结束)
2.每个线程都有一个中断标记位,这个标记位默认是false,
3.当你执行这个线程的interrupt方法后,这个标记位会变为true
while(!Thread.currentThread.isInterrupted())
当你线程处于阻塞的状态下,比如await,wait,在阻塞队列,sleep等等,此时如果被中
断,会抛出InterruptedException
当然也可以直接指定共享变量。
volatile boolean flag = false;
run(){
while(!flag){
// 处理任务!!
}
}
ThreadLocal原理以及内存泄漏问题
1. 在开发中会用到的方式就是实现线程范围的共享变量,等于就是线程内的传递参数和共享
2.ThreadLocal内存泄漏问题:
 2.1 key: key会在玩花活使用ThreadLocal时 ,在局部声明ThreadLocal,局部方法已经执行完
毕,但是线程会指向ThreadLocalMap,ThreadLocalMap的key会指向ThreadLocal对象,这
会导致ThreadLoc会被al对象不回收。所以ThreadLocal在设计时,将key的引用更改为了弱引
用,如果再发生上述情况,此时ThreadLocal只有一个弱引用指向,可以被正常回收。
2.2 value: 如果是普通线程使用ThreadLocal,那其实不remove也不存在问题,因为线程会结
束,销毁,线程一销毁,就没有引用指向ThreadLocalMap了,自然可以回收。但是如果是线程
池中的核心线程使用了ThreadLocal,那使用完毕,必须要remove,因为核心线程不会被销毁
(默认),导致核心线程结束任务后,上一次的业务数据还遗留在内存中,导致内存泄漏问题。
伪共享问题以及处理方案
所谓的伪共享就是多个数据公用一个CPU缓存行产生的问题。
缓存行(cache line)是缓存读取的最小单元,缓存行是 2 的整数幂个连续字节,一般为 32-256 个字节,最常见的缓存行大小是 64 个字节
当一个缓存行的64个字节,缓存了多个数据(ABCD),此时因为JVM的操作,对象A数据被修改了,但是对于CPU来说,我只能知道当前缓存行的数据被修改了,现在的数据不安全,需要重新的去JVM 中将数据同步一次。
因为CPU执行的效率特别快,如果去主内存中同步一次数据,相对CPU的速度来说,就像我们执行查询一次数据库一样,会很影响效率。
想解决这个问题,避免其他线程写缓存行导致当前线程需要去主内存查询,可以让某个线程直接占满当前缓存行的64k大小即可。
占满缓存行,独自使用,其实就是利用空间换时间的套路。
缓存级别越小,越接近CPU,意味着速度越快,容量越小:
缓存行也是缓存一致性协议管理的最小单位,什么是缓存一致性协议MESI?
这是网上找的MESI协议实例:
  1. CPU0 读取 a0
  2. CPU1 写 a0
分析:
  1. CPU0 读取 a0,读到 Cache0 之后,因为独占,所以缓存行的状态是 E。
  2. CPU2 写 a0,先把 a0 读到 Cache2,因为共享,所以状态是 S。然后修改 a0 的值,缓存行的状态变成了 E,最后通知 CPU0 ,将 a0 所在的缓存行失效。

MESI 协议是一个基于失效的缓存一致性协议,是支持写回(write-back)缓存的最常用协议,也是现在一种使用最广泛的缓存一致性协议,它基于总线嗅探实现,用额外的两位给每个缓存行标记状态,并且维护状态的切换,达到缓存一致性的目的。

MESI 状态

MESI 是四个单词的缩写,每个单词分别代表缓存行的一个状态:

  1. M:modified,已修改。缓存行与主存的值不同。如果别的 CPU 内核要读主存这块数据,该缓存行必须回写到主存,状态变为共享状态(S)。

  2. E:exclusive,独占的。缓存行只在当前缓存中,但和主存数据一致。当别的缓存读取它时,状态变为共享;当前写数据时,变为已修改状态(M)。

  3. S:shared,共享的。缓存行也存在于其它缓存中且是干净的。缓存行可以在任意时刻抛弃。

  4. I:invalid,无效的。缓存行是无效的。

MESI 消息

MESI 协议中,缓存行状态的切换依赖消息的传递,MESI 有以下几种消息:

  1. Read: 读取某个地址的数据。

  2. Read Response: Read 消息的响应。

  3. Invalidate: 请求其他 CPU invalid 地址对应的缓存行。

  4. Invalidate Acknowledge: Invalidate 消息的响应。

  5. Read Invalidate: Read + Invalidate 消息的组合消息。

  6. Writeback: 该消息包含要回写到内存的地址和数据。

MESI 通过消息的传递维护了一个缓存状态机,实现共享内存,至于细节是怎么样的,这里不做过多表述。

MESI 的存在保证了缓存一致性,让多核 CPU 能够更好地进行数据交互,但是要注意,并不意味着 CPU能 被压榨到极致。

CPU缓存可见性问题
CPU缓存可见性的问题,就是在缓存行数据发生变化时,会发出通知,告知其他内核缓存行数据设
置为无效。
但是因为CPU厂商为了提升CPU的执行效率,经常会追加一些优化的操作,StoreBuffer-写缓存队列 \Invalidate Queue-无效化队列 。这些就会导致MESI协议通知受到影响,同步数据没那么及时。
所以CPU内部提供了一个指令, lock前缀指令 ,如果使用了lock前缀指定去操作一些变量,此时会 将数据立即写回到主内存(JVM),必然会触发MESI协议,类似StoreBuffer,Invalidate Queue的缓存机制也会立即处理。
  • 42
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值