AtomicInteger对应的方法如下:
调用了C++的如下代码
这里看到有一个LOCK_IF_MP,作用是如果是多处理器,在指令前加上LOCK前缀,因为在单处理器中,是不会存在缓存不一致的问题的,所有线程都在一个CPU上跑,使用同一个缓存区,也就不存在本地内存与主内存不一致的问题,不会造成可见性问题。然而在多核处理器中,共享内存需要从写缓存中刷新到主内存中去,并遵循缓存一致性协议通知其他处理器更新缓存。
Lock在这里的作用:
在cmpxchg执行期间,锁住内存地址[edx],其他处理器不能访问该内存,保证原子性。即使是在32位机器上修改64位的内存也可以保证原子性。
将本处理器上写缓存全部强制写回主存中去,也就是写屏障,保证每个线程的本地内存与主存一致。
禁止cmpxchg与前后任何指令重排序,防止指令重排序。
Java对象头:
在Hotspot虚拟机中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充();Java对象头是实现synchronized的锁对象的基础,一般而言,synchronized使用的锁对象是存储在Java对象头里。它是轻量级锁和偏向锁的关键.
import org.openjdk.jol.info.ClassLayout;
public class test001 {
public static void main(String[] args) {
Object demo = new Object();
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
synchronized (demo) {
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
}
}
可以看到加锁后除了Mawrk Word中不同,其他的都是相同的,后面的001代表的是无锁,00代表轻量级锁,偏向锁未启动,直接升级成轻量级锁
某个应用程序过分的访问这些硬盘,内存等资源,就会导致整个系统的资源被占用,如果不对这种行为进行限制和区分,就会导致资源访问的冲突。所以,Linux的设计的初衷:给不同的操作给与不同的“权限”。Linux操作系统就将权限等级分为了2个等级,分别就是内核态和用户态
锁升级的过程:
级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁
偏向锁:把自己的线程Id放到对象的Mawrk Word中
之前从轻量级锁(自旋锁)升级到重量有两个条件:
1.自旋次数>10,2.自旋的线程>cpu的一半
现在是JVM来决定自旋几次升级
重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。
简言之,就是所有的控制权都交给了操作系统,由操作系统来负责线程间的调度和线程的状态变更。而这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资
偏向锁是jvm启动4秒后自动打开,对照上图101代表的是偏向锁
import org.openjdk.jol.info.ClassLayout;
public class test001 {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
Object demo = new Object();
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
synchronized (demo) {
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
}
}
没有打开偏向锁的时候,是一个无锁状态001,但打开后变成了偏向锁101,可以通过下图来解释这个现象。
synchronized代码块
synchronized代码块在jvm中是monitorenter和monitorexit指令实现,两个monitorexit是因为一个是正常结束,一个异常结束的位置
当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。
synchronized方法
使用javap -v查看jvm信息,synchronized方法时用的
方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor, 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放
参考::
https://blog.csdn.net/javazejian/article/details/72828483
硬件层数据一致性
多线程底层:CPU去访问内存的数据时,会锁住bus,避免多个CPU取数据发生数据不一致的情况
一致性协议 MESI:
为了解决总线锁效率低下的问题,Inter用了MESI协议来处理
https://www.cnblogs.com/z00377750/p/9180644.html
现代CPU解决数据一致性:缓存锁+总线锁
缓存行:
x和y处于一个缓存行(缓存最小单位,64节的数组)
CPU1只读x,但是会读取整个缓存行x和y,当修改了x后,需要通知其他的读取了该行的CPU2
CPU2只读y,但是会读取整个缓存行x和y,当修改了y后,需要通知其他的读取了该行的CPU1
CPU1只关心x,CPU2只关心y,但现在他们相互影响
使用缓存行的对齐来提高效率