synchronized同时对原子性、可见性、有序性的保证

原子性:基本复制写操作都能保证原子性,复杂操作无法保证

可见性:MESI协议的flush、refresh配合使用,解决可见性

有序性:3个层次,最后1个层次有4中内存重排序

synchronized可同时保证:

  • 原子性:有加锁和释放锁的机制,加锁后,同一段代码只有他能执行
  • 可见性:加内存屏障,在同步代码块做变量写操作,在释放锁时,会强制执行flush操作。在获取锁进入同步代码块时,会对变量读强制执行refresh操作。
  • 有序性:加各种内存屏障,解决4种内存重排序

1.synchronized保证原子性的原理

加锁,有一个monitorenter指令。然后对锁对象关联的monitor累加,同时标识本线程已加锁。释放锁,有一个monitorexit指令,递减锁计数器。递减至0说明当前线程不持有锁

wait和notify也是基于monitor实现的。有线程执行wait,会把自己加入到monitor关联的waitset中,等待唤醒获取锁。notifyall会从monitor的waitset中唤醒所有的线程,让他们去竞争锁。

Java对象 = 对象头 + 实例变量(变量数据)

对象头 = Mark Word(hashcode、锁数据) + Class Metadata Address(指向类的元数据的指针)

在这里插入图片描述

3个Thread执行同步代码块,就进入entrylist中。通过CAS加锁,count计数器累加,owner记录是谁持有锁。

执行wait,会把线程放到waitset中,count=0,owner=null,等待 唤醒

执行notifyall,会唤醒waitset中的线程

2.synchronized使用内存屏障保证可见性、有序性

按可见性划分,内存屏障分为:

  • Load屏障:执行refresh,从其他处理器的高速缓冲、主内存,加载数据到自己的高速缓存,保证数据是最新的
  • Store屏障:执行flush操作,自己处理器更新的变量的值,刷新到高速缓存、主内存去
synchronized(this){-->monitorenter
    load内存屏障
    
    int a = b;//读,通过load内存屏障,强制执行refresh,保证读到最新的
    c=1;//写,释放锁时会通过Store,强制flush到高速缓存或主内存
}-->monitorexit
Store内存屏障

按照有序性,内存屏障可分为:

  • Acquire屏障:load屏障之后,加Acquire屏障。它会禁止同步代码块内的读操作,和外面的读写操作发生指令重排
  • Release屏障:禁止写操作,和外面的读写操作发生指令重排
synchronized(this){-->monitorenter
    load内存屏障
    Acquire屏障,禁止代码块内部的读,和外面的读写发生指令重排
    
    int a = b;//读,通过load内存屏障,强制执行refresh,保证读到最新的
    c=1;//写,释放锁时会通过Store,强制flush到高速缓存或主内存
    
    Release屏障,禁止写,和外面的读写发生指令重排
}-->monitorexit
Store内存屏障

Acquire和Release虽然能保证同步代码块内外不会指令重排,但是内部多行代码还是会发送指令重排。


版权声明:本文为qq_36299025原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。