原子性:基本复制写操作都能保证原子性,复杂操作无法保证
可见性: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版权协议,转载请附上原文出处链接和本声明。