个人学习

7.线程安全之可见性

2020-03-03  本文已影响0人  强某某

可见性问题

  1. 代码
// 关闭jit优化:-server -Djava.compiler=NONE

//将运行模式设置为- server服务器端,就会变成死循环,默认idea运行时-client模式不会进行jvm层次的指令重排,也就是JIT时期的重排
//通过设置JVM的参数,打印JIT编译的内容(这里说的编译非class文件,是底层汇编内容),通过可视化工具jitwatch查看
//-server -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation -XX:LogFile=jit.log
public class VisibilityDemo {
    private  boolean flag=true;
    public static void main(String[] args) throws InterruptedException {
        VisibilityDemo demo=new VisibilityDemo();
        new Thread(()->{
            int i=0;
            //class->运行时jit编译->汇编指令->重排序
            while (demo.flag) {//指令重排序导致死循环
                    i++;
            }
//            if (demo.flag) {
//                while (true) {
//                    i++;
//                }
//            }
            System.out.println(i);
        }).start();

        TimeUnit.SECONDS.sleep(2);
        demo.flag=false;
        System.out.println("重置了");
    }
}

说明:在java运行参数加上server时候,为提高性能,会执行jit然后指令重排,导致实际上执行的是注释的代码,从而导致死循环

  1. 工作内存缓存

其实也就是cpu缓存,但是实际上该缓存只会让另外线程晚一点发现变量已经改为false了,而不是导致死循环


1.png
  1. 指令重排
    出了CPU会进行指令重排,Java编程语言的语义允许编译器和微处理器执行优化,这些优化可以与不正确的同步代码交互,从而产生看似矛盾的行为。


    2.png

从上图可知,左边的重排就会导致结果的未可知。

3.png
  1. 共享变量描述
    可以在线程之间共享的内存称为共享内存或堆内存。例如:实例字段,静态字段和数组元素都存储在堆内存中。
  1. Happens-before先行发生原则:下列的时候jit不进行指令重排,同时jit会调用cpu的内存屏障禁止cpu级别的指令重排
    happens-before关系主要用于强调两个有冲突的动作之间的顺序,以及定义数据争用的发生时机。
    具体虚拟机的实现,有必要确保以下原则的成立

管程概念补充

1.     管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。

2.     进程只能互斥得使用管程,即当一个进程使用管程时,另一个进程必须等待。当一个进程使用完管程后,它必须释放管程并唤醒等待管程的某一个进程。

3.     在管程入口处的等待队列称为入口等待队列,由于进程会执行唤醒操作,因此可能有多个等待使用管程的队列,这样的队列称为紧急队列,它的优先级高于等待队列。

针对第二条:如下加上同步关键字则jit就不会指令重排,不会死循环

public static void main(String[] args) throws InterruptedException {
        VisibilityDemo demo=new VisibilityDemo();
        new Thread(()->{
            int i=0;
            //class->运行时jit编译->汇编指令->重排序
            while (demo.flag) {//指令重排序导致死循环
               synchronized (this) {
                    i++;
               }
            }
            System.out.println(i);
        }).start();

        TimeUnit.SECONDS.sleep(2);
        demo.flag=false;
        System.out.println("重置了");
    }
  1. volatile关键字
    可见性问题:让一个线程对共享变量的修改,能够及时的被其他线程看到

根据JMM中规定的happen before和同步原则:
对某个volatile字段的写操作happens-before每个后续对该volatile字段的读操作。
对volatile变量的v的写入,与所有其他线程后续对v的读同步

针对第三条:加上volatile关键字也禁止了指令重排,所以也可以避免死循环

public class VisibilityDemo {
    private volatile boolean flag=true;
    public static void main(String[] args) throws InterruptedException {
        VisibilityDemo demo=new VisibilityDemo();
        new Thread(()->{
            int i=0;
            //class->运行时jit编译->汇编指令->重排序
            while (demo.flag) {//指令重排序导致死循环
                    i++;
            }
            System.out.println(i);
        }).start();

        TimeUnit.SECONDS.sleep(2);
        demo.flag=false;
        System.out.println("重置了");
    }
}

说明:volatile修饰的关键字,则没有缓存的说法了,每次其他线程或者本线程读取的时候都会从主内存读取,而且修饰之后,jit也不会对这部分进行指令重排,从而正常结束

  1. word tearing字节处理


    4.png
  1. double和long的特殊处理


    5.png
  1. final在JMM中处理
public class Final {
    final int x;
    int y;

    public Final(){
         x=1;
         y=x;
        System.out.println(y);
    }

    public static void main(String[] args) {
        Final f=new Final();
    }
}

如果没设置final修饰x,则可能出现y是默认值的情况,如果加了final修饰那就肯定是1

上一篇 下一篇

猜你喜欢

热点阅读