线程的本质(操作系统与CPU是如何执行线程的)

本文深入探讨Java多线程中的线程可见性、有序性问题,以及指令重排的影响。通过实例分析了DCL双检锁模式的隐患,并解释了volatile关键字如何确保内存可见性和有序性。同时,讨论了Jvm中的HappensBefore原则和缓存一致性协议。最后,介绍了如何利用volatile修正并发问题,以及volatile在防止指令重排序中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

线程撕裂者

线程可见性 

线程:有序性

指令重排

DCL写法

如何修正这个问题?volatile

Jvm中不可重排的8种指令 HappensBefore


线程数应该设置多少?

多核CPU

线程撕裂者

寄存器:用来保存计算所需要的数据

ALU:计算单元:维护一组寄存器

多线程:一个CPU维护多组寄存器,需要完成线程切换

从CPU到内存之间:工业上最多有三层缓存

一颗CPU里包含多个CPU核

线程可见性 

public static void main(String[] args) {
    boolean running = true;
    Thread t = new Thread(() ->{
        while(true){
            System.out.print("running");
        }
    });
    t.start();
    t.sleep(2);
    running = false;
}

 即使主线程把running设置为了false,但是线程t并不会立马停止,主要是线程可见性的问题,因为running如果读到核1执行,放到L1缓存中,但是核2又是在其自己的缓存中进行,什么时候重新从内存中读取到核1中,又需要指令的触发。

局部性原理:空间局部性,时间局部性。

工业上读取一次数据是64个字节,当程序需要读取变量x的时候,会一次读取64个字节(cache line)

缓存一致性协议,修改了一个x,通知其他线程读取的这行数据已经失效了

 abstract class RingBufferePad{
        protected long p1;
        protected long p2;
        protected long p3;
        protected long p4;
        protected long p5;
        protected long p6;
        protected long p7;

    }
    RingBufferePad(){
        
    }

volatile:对内存的修改就需要通知所有用到这块内存的线程全都再读取一遍

线程:有序性

在什么情况下会输出x  = 0, y = 0(不可能;只有x = b, y = a, a = 1, b = 1,也就是两句执行换了顺序) 但是多次运行之后,会出现x = 0, y = 0的情况,因为指令重排序现象的发生,重排序的目的只是为了提高效率,压榨CPU,比如(指令1实现i++,指令2需要从内存中读取数据,但指令2在指令1前面,可以在指令2执行的过程中同时执行指令1,主要是为了提高效率)

指令重排

什么情况下能发生重排序?:两条指令换了顺序以后不影响最终一致性。(注意,这个重排序的优化只是针对单线程而存在的)

线程的as-if-serial:看上去像是序列化(单线)执行的

多线程会产生你不希望看到的结果。

new:申请一块内存(占多少字节,就需要申请多大的内存空间),设置默认值,创建一个对象不是原子性的

invokespecial:特殊调用,调用构造方法

astore:用栈里的t和堆里的对象建立关联

临界区;持有这把锁只允许一个线程执行,而且只能单线程执行,如果临界区太大(锁粒度太粗),严重影响效率。

源码(设计模式,多线程)

DCL写法

//业务逻辑代码省略

if(INSTANCE == null){//先判断
    synchronized(Main.class){//再上锁
        if(INSTANCE == null){//再判断一次
            try{
                INSTANCE = new Main();
            }
        }
    }
}

astore和invokespecial发生了交换,t指向一个半初始化的对象。 

如何修正这个问题?volatile

synchronized不能保证指令之间的有序性,只是保证线程之间的有序性。synchronized和非synchronized的代码可以同时执行。

如果用了volatile,为什么第一个检查还需要?:提高性能,如果没有第一个检查,来了10000个线程,那么这些线程都会加入锁的竞争,大大降低效率,但如果我加了判断的话,只要单例被创建出来了,不会出现竞争的情况。

锁加在方法上锁定的是this对象,但如果加上代码上,锁定的是class对象

为什么使用volatile可以禁止指令的重排序?

屏障(fence, barrier):一条特殊指令,不允许上下两条指令换顺序,不同的CPU有不同的屏障指令,JVM也有自己的屏障指令。共4条

load:是读;store:写 loadload:表示的是在读读之间加屏障,不可以换顺序。在JVM中,只要不用volatile就不会加屏障,volatile会加屏障。

Jvm中不可重排的8种指令 HappensBefore

 volatile的作用:保证可见性和有序性

 对于JVM有很多的开源实现

hotspot dragenwell(阿里的实现)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值