Java高级开发

Java线程安全-可见性问题

2020-01-04  本文已影响0人  依弗布德甘

Java内存模型(JMM) 与 JVM运行时数据区

【Java语言和虚拟机规范】
The Java Language Specification
The Java Virtual Machine Specification
访问地址:https://docs.oracle.com/javase/specs/


多线程中的问题

  1. 所见非所得
  2. 无法肉眼检测程序的准确性
  3. 不同的运行平台有不同的表现
  4. 错误很难重现

问题解析

public class Demo {
    int i = 0;
    public boolean isRunning = true;
    
    public static void main(String args[])  throws InterruptedException {
        Demo demo = new Demo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("here i am ...");
                while(demo.isRunning){
                    demo.i ++;
                }
                System.out.println("i=" + demo.i);
            }
        }).start(); 

        Thread.sleep(3000L);
        demo.isRunning = false;
        System.out.println("shutdown...");
     }
}
启动参数 32位JDK 64位JDK
-server 不打印i的值 不打印i的值
-client 打印i的值 不打印i的值

JIT编译器(Just In Time Compiler)

 bool f = isRunning

 if(f) {
    while(true) {
        i++
    }
 }
实际的编译结果-引发无法打印i的结果

volatile关键字

Java内存模型规定:对volatile变量的写入,与所有其他线程后续对变量的读同步
可见性问题:让一个线程对共享变量的修改,能及时的被其他线程看到

public class Demo {
    int i = 0;
    public volatile boolean isRunning = true;
    
    public static void main(String args[])  throws InterruptedException {
        Demo demo = new Demo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("here i am ...");
                while(demo.isRunning){
                    demo.i ++;
                }
                System.out.println("i=" + demo.i);
            }
        }).start(); 

        Thread.sleep(3000L);
        demo.isRunning = false;
        System.out.println("shutdown...");
     }
}

对变量加入volatile关键字修饰后,能打印i的结果

volatile 只会对变量禁止缓存和不指令重排,但是JIT还是可以批量编译,只是不会出现这种“激进”的编译结果


线程理论

共享变量 (Shared Variables定义)

线程间操作定义(约束)

  1. 线程间操作指:一个程序执行的操作可被其他线程感知或者被其他线程直接影响
  2. Java内存模型只描述线程间操作,不描述线程内操作,线程内操作按照线程内语义执行,如局部变量

对同步的规则定义

Happens-before先行发生原则

我们无法就所有场景来规定某个线程修改的变量何时对其他线程可见,但是我们可以指定某些规则,这规则就是happens-before,从JDK 5 开始,JMM就使用happens-before的概念来阐述多线程之间的内存可见性。

  1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
  2. 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

JVM需要实现如下规则:

  1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
  2. 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
  3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
  4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
  5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
  7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
  8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

final在JMM中的处理

public class Demo{
  final int x;
  int y;
  public Demo(){
     x = 3; //能正确的读到 x = 3的构造版本
     y = 4; //看到 y=0 的构造版本
  }
}
public class Demo{
  final int x;
  int y;
  public Demo(){
     x = 3; //能正确的读到 x = 3的构造版本
     y = 4; //也等读到y=3的构造版本
  }
}
上一篇 下一篇

猜你喜欢

热点阅读