多线程线程安全之保证可见性

7 篇文章 0 订阅
6 篇文章 0 订阅
  • 在单线程的环境下,如果向一个变量先写入一个值,然后在没有写干涉的情况下读取这个变量的值,那这个时候读取到的这个变量的值应该是之前写入的那个值。这本来是一个很正常的事情。但是在多线程环境下,读和写发生在不同的线程中的时候,可能会出现:读线程不能及时的读取到其他线程写入的最新的值。这就是所谓的可见性
  • 1.引出问题

一个测试代码:

    public static boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
//      创建线程
        Thread thread = new Thread(() -> {
            int i = 0;
//            判断静态变量是否为true,如果不是则一直执行
            while (!stop) {
                i++;
                System.out.println("rs:" + i);
            }
        });
//        线程启动
        thread.start();
//        睡眠一秒
        Thread.sleep(1000);
//        变更静态变量
        stop = true;
    }

预计结果:

1.主线程执行,2.新线程启动,3.主线程睡眠一秒,4.一秒之后子线程while判断失效结束循环,线程结束。

测试结果:

while一直循环,程序无法停止。

  • 尝试变更代码得到预计结果

1.添加一条打印语句

    public static boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
//      创建线程
        Thread thread = new Thread(() -> {
            int i = 0;
//            判断静态变量是否为true,如果不是则一直执行
            while (!stop) {
                i++;
//                添加一条打印语句
                System.out.println("rs:" + i);
            }
        });
//        线程启动
        thread.start();
//        睡眠一秒
        Thread.sleep(1000);
//        变更静态变量
        stop = true;
    }

执行结果:

程序正常结束。

2.让子线程睡眠

  public static boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
//      创建线程
        Thread thread = new Thread(() -> {
            int i = 0;
//            判断静态变量是否为true,如果不是则一直执行
            while (!stop) {
                i++;
//                让子线程睡眠
                try {
                    Thread.sleep(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
//        线程启动
        thread.start();
//        睡眠一秒
        Thread.sleep(1000);
//        变更静态变量
        stop = true;
    }

执行结果:

程序正常结束

3.对静态变量添加一个 volatile 属性


    //添加volatile属性
    public volatile static boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
//      创建线程
        Thread thread = new Thread(() -> {
            int i = 0;
//            判断静态变量是否为true,如果不是则一直执行
            while (!stop) {
                i++;
            }
        });
//        线程启动
        thread.start();
//        睡眠一秒
        Thread.sleep(1000);
//        变更静态变量
        stop = true;
    }

运行结果:

程序正常结束。

  • 尝试

  1. print就可以导致循环结束

是因为当前安装的java虚拟机是server版本,server版本内部对代码做了一个JIT深度优化(活性失败)。

查询当前jvm版本,如下图:

while (!stop) {
    i++;
}
//这段代码等价于

if (!stop) {
    while (true) {
        i++;
    }
}

如果想要jvm不对代码进行优化可以在启动参数中添加:-Djava.compiler=NONE

原始代码测试运行:

程序正常结束。

查看 System.out.print(); 底层代码,发现调用了

1.IO流

2.synchronized

那我们分别对代码进行 IO操作,和 加锁操作(记得删掉启动参数中的-Djava.compiler=NONE)


    public  static boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
//      创建线程
        Thread thread = new Thread(() -> {
            int i = 0;
//            判断静态变量是否为true,如果不是则一直执行
            while (!stop) {
                i++;
                synchronized (TestDemoDel.class){

                }
            }
        });
//        线程启动
        thread.start();
//        睡眠一秒
        Thread.sleep(1000);
//        变更静态变量
        stop = true;
    }


    public  static boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
//      创建线程
        Thread thread = new Thread(() -> {
            int i = 0;
//            判断静态变量是否为true,如果不是则一直执行
            while (!stop) {
                i++;
                new File("tea.txt") ;
            }
        });
//        线程启动
        thread.start();
//        睡眠一秒
        Thread.sleep(1000);
//        变更静态变量
        stop = true;
    }

 启动,执行。发现也会结束循环,程序停止。

  • 概念

通过对上述代码查看汇编指令,使用HSDIS工具,具体的使用方法自行百度。
可以看到,使用volatile关键字或各种方式使程序停止之后,多了一个Lock指令。

0x00000000037028f3: lock add dword ptr [rsp],0h ;*putstatic stop



0x0000000002b7ddab: push 0ffffffffc4834800h ;*putstatic stop; -

硬件层面
CPU/内存/IO设备

  • CPU层面增加了高速缓存
  • 操作系统,进程、线程、| CPU时间片来切换
  • 编译器的优化 ,更合理的利用CPU的高速缓存.

看下图:

高速缓存分为 L1,L2,L3

  1. L1d 数据缓存
  2. L1i 指令缓存
  3. L2二级缓存
  4. L3三级缓存

离线程越近,速度越高。

当一个线程不存在某条内容数据时,会去内存中加载,加载之后,会读取到CPU的高速缓存中,下一次会先从高速缓存中加载,如果缓存存在,那就可以直接使用。大大加快了线程IO性能的开销,从而提升性能的以及CPU的利用率。

当使用 volatile 关键字时,会对缓存加锁。使其从内存中读取数据。所以会使其线程可见

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值