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

来源:互联网 发布:一叶落而知深秋 编辑:程序博客网 时间:2024/06/06 13:11
  • 什么是Java内存模型
    • 并发程序需要保证多线程间数据访问的一致性。如果一个线程中修改了全局变量A,在另外一个线程中读取到的值未必是修改后的新值。
    • Java内存模型用来将这种看似随机的状态变为可控,来屏蔽多线程间可能引发的问题;
  • 原子性
    • 原子操作不可中断,也不能被多线程干扰;
      • 如:intbyte等数据的赋值操作具备原子特性,而像"a++"这样的操作就不具备原子性,因为它涉及读取a,计算新值和写入a三步操作,中间可能被其他线程干扰,导致最终的计算结果和实际值出现偏差。
  • 有序性
    • 现代的CPU为了支持指令流水线操作,有可能会对目标指令进行重排。重排只对多线程的语义产生影响。
    • 示例:指令重排引起的多线程间的语义冲突

public class OrderExample {

inta =0;

booleanflag = false;

public void writer(){

a =1;

flag =true;

}

public void reader(){

if(flag){

inti =a+1;

}

}

}

假设线程A首先执行writer()方法,接着线程B执行reader()方法,如果发生指令重排,那么线程B在代码第10行时,不一定能看到a已经被赋值为1
下图显示了两个线程的调用关系

  • 解决方案:synchronized

public class OrderExample {

inta =0;

booleanflag = false;

public void writer(){

a =1;

flag =true;

}

public synchronized void reader(){

if(flag){

inti =a+1;

}

}

}

使用synchronized后,由于同步,可以解决这种语义上的冲突,即使线程A进行了指令重排,但在writer()方法执行时,线程B无法进入,只有线程A释放锁,线程B才得以进入,因此,无论线程A的指令执行顺序如何,线程B都会看到相同的最终结果

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

 

public class VolatileTest {

public static class MyThreadextends Thread{

private booleanstop = false;

public void stopMe(){

stop=true;

}

public void run(){

inti =0;

while(!stop){

i++;

}

System.out.println("stop Thread");

}

}

public static void main(String[]args) throws InterruptedException{

MyThreadt = new MyThread();

t.start();

Thread.sleep(1000);

t.stopMe();

Thread.sleep(1000);

}

}

此段代码开启两个线程,主线程和MyThread线程,在主线程中使用stopMe()方法修改stop变量通知MyThread线程结束。使用-server参数执行这段代码(由于server虚拟机会做足够多的优化,可将多线程的可见性问题表现得更明显),结果发现,MyThread始终无法结束。这就是由于在主线程中对stop变量的修改无法反应到MyThread线程中去

解决方案1:将第3行代码修改为(增加volatile关键字):
private volatile boolean stop = false;

解决方案2:使用synchronized关键字:

public class MyThreadextends Thread {

private booleanstop = false;

public synchronized void stopMe(){

stop =true;

}

public synchronized boolean stopped(){

returnstop;

}

public void run(){

inti = 0;

while(!stopped()){

i++;

}

System.out.println("Stop Thread");

}

}

分析:MyThread可以接收到停止命令,将线程退出。这也说明了synchronized不仅可以用于线程同步控制,还可以用于解决可见性问题

  • Happens-Before原则
    • 虚拟机和执行系统释放指令重排是有原则的,下面的原则是指令重排不可违背的:
      • 程序顺序原则:一个线程内保证语义的串行性;
      • volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性;
      • 锁规则:解锁必然发生在随后的加锁前;
      • 传递性:A先于BB先于C,那么A必然先于C
      • 线程的start()方法先于它的每一个动作;
      • 线程的所有操作先于线程的终结;
      • 线程的终端先于被中断线程的代码;
      • 对象的构造函数执行结束先于finalize()方法;
0 0