golang Sync.Mutex互斥锁和Sync.RWMutex读写锁小结,反射为什么慢??? java中的自动装箱与拆箱

Sync.Mutex

一、结构体

type Mutex struct {
  state int32  // 互斥锁的状态:被g持有,空闲等
  sema  uint32 // 信号量,用于阻塞/唤醒 goroutine(协程)
}
//使用
  var mtx sync.Mutex
  mtx.Lock()
  mtx.Unlock()

这里的 state 字段是 int32 类型,但是它被分为了4 部分操作,为了使用更少的内存去表达锁的各个状态

二、正常模式和饥饿模式

正常模式自旋和排队同时存在的,执行Lock的goroutine会先一边自旋,尝试过几次后还没拿到锁,就需要去排队等待了。这种在排队之前先让大家来抢的模式,能够有更高的吞吐量,因为频繁的挂起、唤醒goroutine会带来较多的开销。但又不能无限制的自旋,要把自旋的开销控制在较小的范围内。所以在正常模式下,Mutex有更好的性能,但是可能会出现队列尾端的goroutine迟迟抢不到锁(尾端延迟)的情况。而饥饿模式下不在自旋尝试,所有goroutine都要排队,严格的先来后到,对于防止出现尾端延迟来讲特别重要

  1. normal 模式下:
    1. 加入了竞争的情况,被唤醒的 g 可能会与刚到来的 g 一起竞争锁,但是被唤醒的 g 很可能失败。因为 g 被唤醒就说明锁已经被释放了,那么自旋的很可能已经获得锁了
    2. 睡眠时间超过 1ms 的 g,被唤醒后想要将 mutex 切换为 starving 模式,切换后也会再次进入阻塞队列且排在队列头部,等待锁的释放别唤醒
  2. starving 模式下:
    1. 只有被唤醒的等待者才能加锁,其他的 g 全都进入 FIFO 阻塞队列

谨记:在原子操作处,所有的 g 都会是串行化(序列化)的。

三、Mutex.Lock()

原子操作atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked)

原子操作确保其它处理器或者核总是访问原子操作之后的最新的值;虽然有 cache 的存在,内存屏障解决,必须要要等到内存屏障管道中的未完成数据都刷新到内存中,再进行操作,而且此操作会让相关的 cpu 的缓存行失效(cpu 缓存一致性协议)。

四、Mutex.UnLock()

所以尽量做到谁 Lock 谁 UnLock,做到谁申请,就由谁释放。一般都是在同一个方法中使用。

Unlock 方法可以被任意的 goroutine 调用释放锁,即使是没持有这个互斥锁的 goroutine,也可以进行这个操作。这是因为,Mutex 本身并没有包含持有这把锁的 goroutine 的信息,所以,Unlock 也不会对此进行检查。

Sync.RWMutex

一、结构体

type RWMutex struct {
w           Mutex  // 互斥锁

writerSem   uint32 // reader 完成后会释放 writer信号量

readerSem   uint32 // writer 完成后会释放 reader 信号量

readerCount int32  // 当前的 reader 的数量(以及用来判断是否有 writer 线程请求锁或者持有锁)

readerWait  int32  // writer 等待 reader 完成的数量,到 0,writer 会被唤醒
}


const rwmutexMaxReaders = 1 << 30 // 表识最大 reader 线程数量

//使用

RLock、RUnLock、Lock、UnLock

RLock方法将rw锁定为读取状态,禁止其他线程写入,但不禁止读取。

反射为什么慢

  1. 反射调用过程中会产生大量的临时对象,这些对象会占用内存,可能会导致频繁 gc,从而影响性能。
  2. 反射调用方法时会从方法数组中遍历查找,并且会检查可见性等操作会耗时。
  3. 反射在达到一定次数时,会动态编写字节码并加载到内存中,这个字节码没有经过编译器优化,也不能享受JIT优化。
  4. 反射一般会涉及自动装箱/拆箱和类型转换,都会带来一定的资源开销。

参考

链接:https://juejin.cn/post/6844904098207105038

java中的自动装箱与拆箱

简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

posted @ 2022-08-16 15:04  凌易说-lingyisay  阅读(144)  评论(0编辑  收藏  举报