Java虚拟机--Java内存模型(十六)

 2019-12-10 16:12  阅读(1063)
文章分类:Java Core
  • 什么是Java内存模型

    • 并发程序需要保证多线程间数据访问的一致性。如果一个线程中修改了全局变量A,在另外一个线程中读取到的值未必是修改后的新值。
    • Java内存模型用来将这种看似随机的状态变为可控,来屏蔽多线程间可能引发的问题;
  • 原子性

    • 原子操作不可中断,也不能被多线程干扰;

      • 如:int和byte等数据的赋值操作具备原子特性,而像“a++”这样的操作就不具备原子性,因为它涉及读取a,计算新值和写入a三步操作,中间可能被其他线程干扰,导致最终的计算结果和实际值出现偏差。
  • 有序性

    • 现代的CPU为了支持指令流水线操作,有可能会对目标指令进行重排。重排只对多线程的语义产生影响。
    • 示例:指令重排引起的多线程间的语义冲突
publicclassOrderExample{inta=0;booleanflag=false;publicvoidwriter(){a=1;flag=true;}publicvoidreader(){if(flag){inti=a+1;}}} 假设线程A首先执行writer()方法,接着线程B执行reader()方法,如果发生指令重排,那么线程B在代码第10行时,不一定能看到a已经被赋值为1了下图显示了两个线程的调用关系
publicclassOrderExample{inta=0;booleanflag=false;publicvoidwriter(){a=1;flag=true;}publicvoidreader(){if(flag){inti=a+1;}}} 假设线程A首先执行writer()方法,接着线程B执行reader()方法,如果发生指令重排,那么线程B在代码第10行时,不一定能看到a已经被赋值为1了下图显示了两个线程的调用关系
  • 解决方案:synchronized:
publicclassOrderExample{inta=0;booleanflag=false;publicvoidwriter(){a=1;flag=true;}publicsynchronizedvoidreader(){if(flag){inti=a+1;}}} 使用synchronized后,由于同步,可以解决这种语义上的冲突,即使线程A进行了指令重排,但在writer()方法执行时,线程B无法进入,只有线程A释放锁,线程B才得以进入,因此,无论线程A的指令执行顺序如何,线程B都会看到相同的最终结果
publicclassOrderExample{inta=0;booleanflag=false;publicvoidwriter(){a=1;flag=true;}publicsynchronizedvoidreader(){if(flag){inti=a+1;}}} 使用synchronized后,由于同步,可以解决这种语义上的冲突,即使线程A进行了指令重排,但在writer()方法执行时,线程B无法进入,只有线程A释放锁,线程B才得以进入,因此,无论线程A的指令执行顺序如何,线程B都会看到相同的最终结果
  • 可见性

    • 可见性是指当一个线程修改了一个变量的值,另外的线程可以马上得知这个修改。

      • 指令重排有可能使得一个线程无法立即得知一个变量的修改;
      • 部分变量的值可能会被寄存器或者高速缓冲缓存,而每个CPU都拥有独立的寄存器和Cache,从而导致其他线程无法立即发现这个修改;
    • 示例:多线程间的可见性问题

|  |publicclassVolatileTest{publicstaticclassMyThreadextendsThread{privatebooleanstop=false;publicvoidstopMe(){stop=true;}publicvoidrun(){inti=0;while(!stop){i++;}System.out.println(“stopThread”);}}publicstaticvoidmain(String[]args)throwsInterruptedException{MyThreadt=newMyThread();t.start();Thread.sleep(1000);t.stopMe();Thread.sleep(1000);}}|此段代码开启两个线程,主线程和MyThread线程,在主线程中使用stopMe()方法修改stop变量通知MyThread线程结束。使用-server参数执行这段代码(由于server虚拟机会做足够多的优化,可将多线程的可见性问题表现得更明显),结果发现,MyThread始终无法结束。这就是由于在主线程中对stop变量的修改无法反应到MyThread线程中去||:-----:|:-----:||publicclassVolatileTest{publicstaticclassMyThreadextendsThread{privatebooleanstop=false;publicvoidstopMe(){stop=true;}publicvoidrun(){inti=0;while(!stop){i++;}System.out.println(“stopThread”);}}publicstaticvoidmain(String[]args)throwsInterruptedException{MyThreadt=newMyThread();t.start();Thread.sleep(1000);t.stopMe();Thread.sleep(1000);}}|此段代码开启两个线程,主线程和MyThread线程,在主线程中使用stopMe()方法修改stop变量通知MyThread线程结束。使用-server参数执行这段代码(由于server虚拟机会做足够多的优化,可将多线程的可见性问题表现得更明显),结果发现,MyThread始终无法结束。这就是由于在主线程中对stop变量的修改无法反应到MyThread线程中去|| | :-----: | |  |publicclassVolatileTest{publicstaticclassMyThreadextendsThread{privatebooleanstop=false;publicvoidstopMe(){stop=true;}publicvoidrun(){inti=0;while(!stop){i++;}System.out.println(“stopThread”);}}publicstaticvoidmain(String[]args)throwsInterruptedException{MyThreadt=newMyThread();t.start();Thread.sleep(1000);t.stopMe();Thread.sleep(1000);}}|此段代码开启两个线程,主线程和MyThread线程,在主线程中使用stopMe()方法修改stop变量通知MyThread线程结束。使用-server参数执行这段代码(由于server虚拟机会做足够多的优化,可将多线程的可见性问题表现得更明显),结果发现,MyThread始终无法结束。这就是由于在主线程中对stop变量的修改无法反应到MyThread线程中去||:-----:|:-----:||publicclassVolatileTest{publicstaticclassMyThreadextendsThread{privatebooleanstop=false;publicvoidstopMe(){stop=true;}publicvoidrun(){inti=0;while(!stop){i++;}System.out.println(“stopThread”);}}publicstaticvoidmain(String[]args)throwsInterruptedException{MyThreadt=newMyThread();t.start();Thread.sleep(1000);t.stopMe();Thread.sleep(1000);}}|此段代码开启两个线程,主线程和MyThread线程,在主线程中使用stopMe()方法修改stop变量通知MyThread线程结束。使用-server参数执行这段代码(由于server虚拟机会做足够多的优化,可将多线程的可见性问题表现得更明显),结果发现,MyThread始终无法结束。这就是由于在主线程中对stop变量的修改无法反应到MyThread线程中去| | | 解决方案1:将第3行代码修改为(增加volatile关键字):privatevolatilebooleanstop=false; | | 解决方案2:使用synchronized关键字:|publicclassMyThreadextendsThread{privatebooleanstop=false;publicsynchronizedvoidstopMe(){stop=true;}publicsynchronizedbooleanstopped(){returnstop;}publicvoidrun(){inti=0;while(!stopped()){i++;}System.out.println(“StopThread”);}}|分析:MyThread可以接收到停止命令,将线程退出。这也说明了synchronized不仅可以用于线程同步控制,还可以用于解决可见性问题||:-----:|:-----:||publicclassMyThreadextendsThread{privatebooleanstop=false;publicsynchronizedvoidstopMe(){stop=true;}publicsynchronizedbooleanstopped(){returnstop;}publicvoidrun(){inti=0;while(!stopped()){i++;}System.out.println(“StopThread”);}}|分析:MyThread可以接收到停止命令,将线程退出。这也说明了synchronized不仅可以用于线程同步控制,还可以用于解决可见性问题| |

  • Happens-Before原则

    • 虚拟机和执行系统释放指令重排是有原则的,下面的原则是指令重排不可违背的:

      • 程序顺序原则:一个线程内保证语义的串行性;
      • volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性;
      • 锁规则:解锁必然发生在随后的加锁前;
      • 传递性:A先于B,B先于C,那么A必然先于C;
      • 线程的start()方法先于它的每一个动作;
      • 线程的所有操作先于线程的终结;
      • 线程的终端先于被中断线程的代码;
      • 对象的构造函数执行结束先于finalize()方法;
点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Java虚拟机--Java内存模型(十六)

相关推荐