从汇编层面理解java关键字volatile

最近对Java的volatile关键字做了一些研究,并且做了系列实验做出一些总结若有不对的地方请指正。
volatile这个应该是java中最不好理解的关键字之一,下面少将概念,多以代码和实验结果去理解volatile。
下面从为什么要引入volatile(why),volatile原理(what)对其两方面理解。
实验环境
wind10,openjdk8,idea,hsdis插件

什么要引入volatile
两个线程A、B共享变量a,当线程A对线程a变量该修改了,B线程无法感知到。

public class TestVolatile1 {
    public static   boolean stop=false;

    public static void testStop()  {
        while (!stop){
        }
    }
    public static  void  stop(){
        stop=true;
    }

    public static void main(String[] args) {

        Thread t1 = new Thread(()-> {
            testStop();
        } );

        Thread t2= new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop();
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("主程序退出");

   }
}

结果会发现,线程t1一直在循环,无法感知到线程t2对公共变量stop的循环。
修改代码将stop加上volatile。

public class TestVolatile1 {
    public static  volatile boolean stop=false;

    public static void testStop()  {
        while (!stop){
        }
    }
    public static  void  stop(){
        stop=true;
    }

    public static void main(String[] args) {

        Thread t1 = new Thread(()-> {
            testStop();
        } );

        Thread t2= new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop();
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("主程序退出");

   }

}

主线程正确退出。
通过以上实验可以证明volatile可以让多线程间可见性得以保证。

volatile语义

  1. 在java语言中volatile有两层语义

    1.保证共享变量的可见性
    2.禁止指令重排

  2. 在C语言中volatile只有一层语义

    1.保证共享变量的可见性

实现原理
1.可见性如何保证
cpu层面的保证,缓存一致性协议如mesi协议。
编译器层面,编译成机器指定每次从内存取值,不用寄存器缓存值,每次从内存里取数据。

2.保证顺序性
防止jvm和操作系统层面的指令重排
在存储volatile变量写入后lock add DWORD PTR [rsp],0x0;

  0x00000000030546e9: mov    edi,0x1
  0x00000000030546ee: mov    BYTE PTR [rsi+0x69],dil
  0x00000000030546f2: lock add DWORD PTR [rsp],0x0  ;                                     com.iboxpay.concurrent.TestVolatile1::stop@5 (line 16

为什么JVM中要加入禁止指令重排指令的语义
因为JVM在做程序优化的做指令重排可能导致共享变量的不可见性。
还是代码1,使用hsdis插件,从汇编层面分析jit生成的机器码

-Xcomp
-XX:+UnlockDiagnosticVMOptions
-XX:PrintAssemblyOptions=intel,hsdis-help
-XX:OnStackReplacePercentage=1400000000
-XX:CompileCommand=dontinline,*TestVolatile1.testStop
-XX:CompileCommand=compileonly,*TestVolatile1.testStop
-XX:CompileCommand=dontinline,*TestVolatile1.stop
-XX:CompileCommand=compileonly,*TestVolatile1.stop
-XX:+PrintAssembly
-XX:+LogCompilation
-XX:LogFile=TestVolatile1.log

在这里插入图片描述
在这里插入图片描述
可以看出在while循环到一定次数后,C2生成了机器码死循环一直在做安全点检查,
不知道这个是否是hotspot虚拟机故意为之,还是其中存在缺陷。
为此测试了下jdk10配置了Graal编译器,生成了同样的死循环。

XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler

将即时编译的级别调到1,程序可以正常退出,主要是C2编译器的优化导致了死循环。

-XX:TieredStopAtLevel=1

代码2加入上volatile属性,java代码对应的汇编如下
在这里插入图片描述
在这里插入图片描述
由上看出程序可以正常退出。
其实在while循环中加入volatile也可以防止其空循环

public class TestVolatile1 {
    public static   boolean stop=false;
    long p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11;
    public static  volatile boolean stop2=false;

    public static void testStop()  {
        boolean temp_stop=false;
        while (!stop){
            temp_stop=stop;
        }
    }
    public static  void  stop(){
        stop=true;
        stop2=true;
    }

    public static void main(String[] args) {

        Thread t1 = new Thread(()-> {
            testStop();
        } );

        Thread t2= new Thread(()->{
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop();
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        }catch (Exception e){
            e.printStackTrace();
        }
   }

}

java对应的机器码
在这里插入图片描述
总结
作用
volatile保证多线程共享变量的可见性
实现原理
cpu层面,有缓存一直性协议保证,如x86
编译层面,机器码直接使用内存值寄存器不缓存
jvm及时编译, 如x86 lock add DWORD PTR [rsp],0x0,防止指令重排,刷新缓存。

03-19
### IEEE 802.1Q VLAN Tagging Protocol Standard IEEE 802.1Q 是支持虚拟局域网(VLAN)的标准协议之一,通常被称为 Dot1q。该标准定义了一种用于以太网帧的 VLAN 标记系统以及交换机和桥接器处理这些标记帧的操作流程[^2]。 #### 协议结构概述 IEEE 802.1Q 的核心功能在于通过在以太网数据帧中插入特定字段来实现 VLAN 标签的功能。这种标签使得网络设备能够识别哪些流量属于哪个 VLAN,并据此执行转发决策。具体来说: - **Tag Header**: 在原始以太网帧头部增加了一个额外的 4 字节字段作为 VLAN 标签头。这四个字节包含了以下部分: - **Priority Code Point (PCP)**: 使用 3 比特表示优先级级别,范围从 0 到 7,主要用于 QoS 控制。 - **Canonical Format Indicator (CFI)**: 这是一个单比特位,在传统以太网环境中设置为零。 - **VLAN Identifier (VID)**: 使用 12 比特标识具体的 VLAN ID,理论上可以支持多达 4096 个不同的 VLAN(编号从 0 至 4095),其中某些特殊值保留给内部用途或管理目的。 #### 数据包处理机制 当一个带有 VLAN tag 的数据包进入支持 IEEE 802.1Q 的交换机时,它会依据此标签决定如何路由或者过滤该数据流。如果目标端口不属于同一 VLAN,则不会传输至其他无关联的物理接口上;反之亦然——只有相同 VLAN 成员之间才允许互相通信除非经过路由器跨网段访问[^1]。 此外,为了简化管理和配置过程并增强互操作性,还引入了一些辅助性的子协议和服务组件比如 GARP(通用属性注册协议)。GARP 可帮助分发有关 VLAN 成员资格的信息到各个连接节点以便动态调整其行为模式而无需频繁手动干预[^3]。 以下是创建带 VLAN TAG 的 Python 示例代码片段展示如何模拟构建这样的 Ethernet Frame: ```python from scapy.all import Ether, Dot1Q, IP, sendp def create_vlan_packet(src_mac="00:aa:bb:cc:dd:ee", dst_mac="ff:ff:ff:ff:ff:ff", vlan_id=100, src_ip="192.168.1.1", dst_ip="192.168.1.2"): ether = Ether(src=src_mac, dst=dst_mac) dot1q = Dot1Q(vlan=vlan_id) ip_layer = IP(src=src_ip, dst=dst_ip) packet = ether / dot1q / ip_layer return packet packet = create_vlan_packet() sendp(packet, iface="eth0") # Replace 'eth0' with your network interface name. ``` 上述脚本利用 Scapy 库生成包含指定源地址、目的地址及所属 VLAN 编号的数据报文并通过选定的网卡发送出去测试实际效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值