线程---volatile

25 篇文章 0 订阅

volatile

  • 定义:
    轻量级的线程同步(sychronized)关键字
  • 特点:
    1.内存可见性对于线程共享变量,一个线程的修改,另一个线程可以直接获取到修改过的值。
    2.禁止指令的重排序(有序性);
  • 优势:
    如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。
    Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
  • volatile如何保证可见性???
    例:
    统计一秒内可以进行多少次加的操作。
public class VolatileDemo0409 {
    static  boolean flag = false;
    static class subThread extends  Thread{

        @Override
        public void run() {
            System.out.println("子线程开始执行");
            int i = 0;
            while (!flag){
                i ++;
            }
            System.out.println("子线程结束 i:"+ i);
        }
    }
    public static void main(String[] args) {
        System.out.println("主线程开始执行");
        new subThread().start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("主线程结束");
    }

}

output:
在这里插入图片描述
可见,程序一直没有结束,当程序运行时,JVM会给每个任务执行的线程分配一个独立的缓存空间,用于提高效率。
下面来阐述一下为什么程序中断,且运行一直没有停下来,每个线程在运行过程中都有自己的工作内存,那么主线程在运行的时候,会将stop变量的值拷贝一份放在内存当中,之后将启动子线程,将flag更改,但是子线程就没有接收到flag更改的信息,所以会一直循环下去。
  
使用volatile修饰flag后,有什么不一样??

static volatile boolean flag = false;

看程序的运行结果:
在这里插入图片描述

可以看到子线程接收到了flag的改变,并正确打印出我们要的结果,接着通过对代码的反编译:在.java编译生成的.class文件中,在字节码文件中加了volatile修饰的变量会加上flags:ACC_VOLATILE,在底层汇编语言上就是讲该变量前添加#Lock前缀
如下:

在这里插入图片描述
在这里插入图片描述

Lock前缀的指令在多核处理器下会引发了两件事情。
1)将当前处理器缓存行的数据写回到系统内存。
2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
拓展
在早期的CPU当中,是通过在总线上加LOCK#锁的形式来解决缓存不一致的问题。因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。由于在锁住总线期间,其他CPU无法访问内存,导致效率低下。
所以就出现了缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
在这里插入图片描述

具体实现:

  1. 使用volatile关键字会强制将修改的值立即写入主存;
  2. 使用volatile关键字的话,当主线程进行修改时,会导致子线程的工作内存中缓存变量flag的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
  3. 由于子线程的工作内存中缓存变量flag的缓存行无效,所以子线程会在主存中去再次读取变量flag的值。
  • 总结volatile作用
    (1)写过程:

1.将修改后的内容写到cache(工作内存)中,当前变量是volatile修饰,会立即写入到主内存;

2.其他线程的工作内存检测总线(一致性协议)上变量有被修改,则置为无效;

(2)读过程:

1.其他线程在读取时,在自己的工作内存中先检测该变量是否有效,

有效时(当前变量其他线程没有修改),则直接使用工作内存中的内容,

无效时(其他线程对该变量做了修改),则直接在主内存中读取最新的内容到工作内存。

  • volatile劣势:

1.频繁更改、改变或写入volatile字段有可能导致性能低下。

2.限制现代JVM的JIT编译器对这个字段优化(volatile字段必须遵守一定顺序,但这也是优点,或者说是特点吧!本来就是要保证happen-before,放置顺序指令重排导致bug 例如单例双检测bug)
—解决办法:
减少对volatile的写操作;重构避免使用volatile。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值