轻松理解volatile(从汇编角度),一看就会

回来填坑:下边说的作用只是volatile保证了goon的可见性,即如果有一个线程修改了共享变量,则会立即让其他线程都知道。如下例子,在主线程中修改了线程类的变量goon的值,每个线程都有属于自己的goon,所以那个goon被在主线程中赋值为false,那个线程就会结束。

补充一下:volatile还有一个作用就是保证有序性,在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。volatile 关键字通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前。

每天进步多一点

假装下边是分割线

--------------------------------------------------------------------------------------------------------------

在网上也有很多关于volatile关键字的文章,看了很多,经过亲自测试,在此写下我的见解:

volatile关键字的作用:拒绝优化!

 

在java中,会将一些经常需要读写的成员拿到寄存器去进行运算,以此来加快运算速度。但是此操作并不是线程安全的,在多线程中,会有不同的线程对该成员进行操作,当该成员被拿到寄存器进行运算优化后,就不能时时刻刻保证与主内存的值同步,因此在多线程中操作时可能会产生错误。当一个成员被volatile关键字修饰时,就会拒绝优化:拒绝将该成员拿到寄存器去进行运算优化。以此来避免多线程不安全的问题。

 

举个很简单的例子:for(i = 0; i < 10; i++) {}

这个简单的操作,在没有加volatile关键字,i 被拿到寄存器进行运算优化:

假设i为一个局部变量,他的偏移量是-4,则 i 的首地址应该是edp[-4];

对应的汇编语言如下:

             mov ecx, edp[-4]
             loop:
             inc ecx
             cmp ecx, 10
             jl loop:

大概意思就是从首地址为-4的区域取出值,赋值给ecx,然后给ecx加一,用ecx和10作比较,如果小于10,就继续循环。

 如果对变量i增加volitale关键字,则,将使用下面的汇编:
             loop:
             mov ecx, edp[-4]       
             inc ecx               
             mov edp[-4], ecx  
             cmp ecx, 10         
             jl loop

这段意思是说:从首地址未-4的区域取出值,赋值给ecx,给ecx加一,然后再将ecx的值赋值给原地址,即edp[-4],然后使用ecx和10进行比较,如果小于10,则继续进入循环。

由此可见两者只有一句话不同,那就是有没有将改变后的值同步更新到源地址。未加volatile关键字的变量在寄存器中运算的时候使用的是ecx,而自己本身的值并没有变。加了volatile关键字的变量会在运算结束后,同步更新原来的值,然后再进行下一步操作。因为寄存器更注重运算速度,所以会省去同步值得步骤。由此可知:加了volatile关键字的变量,其运算速度会大打折扣。

 

下边举一个我做的小测试,很能说明问题:

编写一个线程类,在构造方法中启动该线程, 使用一个Boolean类型的成员goon来控制线程的结束。此处goon未加volatile关键字:

package com.yc.test;
/*
 *@author yc
 *@time  2018/11/14
 */
public class TaskRunByThread implements Runnable{
    private static int id;
    private Boolean goon;

    public TaskRunByThread() {
        goon = true;
        new Thread(this,"[线程" + ++id + "]").start();
    }

    public void stopThread() {
        System.out.println("收到关闭线程的命令");
        goon = false;
    }

    @Override
    public void run() {
        System.out.println("线程" + Thread.currentThread().getName() + "开始运行");
        while (goon) {
        }
        System.out.println("线程" + Thread.currentThread().getName() + "运行结束");
    }
}

编写一个简单的测试类,将上边的线程启动三次,然后每隔随机时间关闭一个线程。

package com.yc.test;

import java.util.Random;
/*
 *@author yc
 *@time  2018/11/14
 */
public class Test {
    public static void main(String[] args) {
        Random random = new Random();
        TaskRunByThread trbts[] = new TaskRunByThread[3];

        //注册并启动线程
        for (int i = 0; i < trbts.length; i++) {
            trbts[i] = new TaskRunByThread();
        }

        for (int i = 0; i < trbts.length; i++) {
            int num = random.nextInt(5000);
            System.out.println("时间间隔:" + num);
            try {
                Thread.sleep(num);
                trbts[i].stopThread();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

运行该测试类,

 可以很清楚的看到:三个线程均启动,并且关闭线程的方法已运行,但从状态栏可以很清楚的看到,三个线程一直在跑,并没有结束。

是因为goon成员未被volatile关键字修饰,被拿到寄存器运算优化,但寄存器的值没有和主内存同步,因此在关闭线程的方法对goon赋值的操作并没有同步到主内存,因此线程无法关闭。

接下来让我们加上volatile关键字再来试一下:

 

可以很明白的看见,三个线程全部运行完毕且关闭。在运行的时候很明显经过间隔时间线程就会关闭。

每天进步多一点

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值