看一遍就懂,详解java多线程——volatile

多线程一直以来都是面试必考点,而volatile、synchronized也是必问点,这里我试图用容易理解的方式来解释一下volatile。

来看一下它的最大特点和作用:

一 使变量在多个线程间可见

猛一看很奇怪,我定义个变量就好了,大家都能访问啊,为毛在多个线程间会有变量不可见?
换种说法,我在一个线程里去修改另外一个线程的变量,可能会修改不成功!而且是永远不成功。
这下更懵逼了,为毛?
来看一下java的内存模型简易图


这个图我来解释一下,先看堆内存区域(被所有线程共享)这个地方。
首先我们搞明白堆里放什么,然后搞明白哪些地方的内存的值是被所有线程共享的。
堆里放的是对象本身,还有数组,不放基本类型(局部变量)和对象的引用(指针)。譬如Person p = new Person();这一下之后,如果Person类里有多个属性int age、double weight、String name什么的,那么在堆里会为Person分配一块内存来装下它所有的属性,然后用一个指针指向这块内存地址,就是p,p会放到栈里去。通过p这么一个指针,你就可以找到Person这么一大块内存了。注意,前面说了堆里不放基本类型是指方法里的局部变量,而类的属性是全局变量。这一块内存是被所有线程共享的。
还有个内存区域也是所有线程共享的,就是方法区。方法区放的是static变量(全局唯一一份)和class类信息(类名、包名、方法名、修饰符public等等),还存在java中很特殊的东西——String,也就是所谓的String常量池。
资料:
http://www.cnblogs.com/wangguoning/p/6109377.html;
http://www.cnblogs.com/whgw/archive/2011/09/29/2194997.html
http://blog.csdn.net/shang02/article/details/51966939
http://www.cnblogs.com/xiohao/p/4278173.html
这些变量是所有线程共享的,so what?
这下就用的上上面那个图了。我们所谓的多线程问题,线程不安全之类的指的就是同一个变量被多个线程同时操作时会发生数据不同步的情况。如果只有一份数据,大家操作的都是同一个值,怎么会不同步呢,为毛呢?
很简单,因为操作的不是同一个数据。
1 每个线程都有一个自己的本地内存空间,线程执行时,先从共享内存区域中取到共享变量,然后干自己的事
2 事毕,裤子穿好,然后在某个时间再把变量刷新回主内存
看到了吧,这是有时间差的,你从主内存里取到的值不见得什么时候被替换了,这样就不同步了,你可能不小心操作的就不是本人而是它双胞胎妹妹了。单线程因为只有一个线程去修改,所以没问题。
而每个线程所维护的这个共享变量的副本可是不开放的,只有自己可见。
证明一下:
  1. public class OneThread extends Thread {  
  2.     private boolean running = true;  
  3.   
  4.   
  5.     @Override  
  6.     public void run() {  
  7.         System.out.println(”进入run方法”);  
  8.         while (running) {  
  9.   
  10.         }  
  11.         System.out.println(”线程执行完毕”);  
  12.     }  
  13.   
  14.     public void setRunning(boolean running) {  
  15.         this.running = running;  
  16.     }  
  17. }  

测试类
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) throws InterruptedException {  
  4.         OneThread oneThread = new OneThread();  
  5.         oneThread.start();  
  6.         Thread.sleep(1000);  
  7.         oneThread.setRunning(false);  
  8.   
  9.     }  
  10.   
  11. }  
在OneThread类中有一个全局变量running,它会进到堆里,被所有线程共享。
Test类中,main是主线程,这样就有两个线程去操作running这个变量。
倘若running是唯一的一份,所有的线程都操作的是同一个running变量,那么当在main中setRunning false后,OneThread就会退出死循环并打印“线程执行完毕”。
我们运行Test


然而,它死循环了……running并没有被修改。
这里要提一下JVM -server模式和JVM -client方式,看这篇讲区别http://blog.csdn.net/zhuyijian135757/article/details/38391785
我的是64位的java,通过java -version确认是JVM server模式,64位只支持server VM。

通过上面的JVM内存模型的图可知,当main线程试图访问running变量时,会先从主内存复制一份到自己的线程内存,修改为false后再刷新回主内存。
刷新回去是没问题,问题是JVM在server模式下,线程会一直在私有内存空间中去读取running变量,也就是说OneThread线程它一直读的是自己复制出来的running,它不会再去读主内存被修改过的running了。这就是问题所在。
为了证明running已经被main修改成功了,我们再加一个线程来看看running的值
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) throws InterruptedException {  
  4.         OneThread oneThread = new OneThread();  
  5.         oneThread.start();  
  6.         Thread.sleep(1000);  
  7.         oneThread.setRunning(false);  
  8.   
  9.         new Thread(() -> System.out.println(oneThread.isRunning())).start();  
  10.     }  
  11.   
  12. }  
这里我们再起一个线程去读取running的值,这时读取的就是主内存的值了。



可以看到false已经打印了,但是死循环还在进行中。说明,OneThread自打复制了running的值到自己的线程空间后,就没再改过了,一直死循环。
那么,我们可以说,线程间的变量是不可见的。
这个问题怎么解决呢?是不是有人想说,static,static不是独一份吗,那么可以去试一下将running变成public static。
结果发现然并卵,static也阻止不了这个死循环。为毛?还是最上面的JVM的图,里面说过了,堆里的和方法区里的都是多线程共享,static是在方法区的,和堆里的效果没区别。
这下怎么办,OneThread根本不认外界的修改,其实也不是了,是因为这个例子比较特殊,是个死循环,我们稍微修改一下
  1. public class OneThread extends Thread {  
  2.     private boolean running = true;  
  3.   
  4.   
  5.     @Override  
  6.     public void run() {  
  7.         System.out.println(”进入run方法”);  
  8.         try {  
  9.             Thread.sleep(1100);  
  10.             System.out.println(running);  
  11.         } catch (InterruptedException e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.         System.out.println(”线程执行完毕”);  
  15.     }  
  16.   
  17.     public void setRunning(boolean running) {  
  18.         this.running = running;  
  19.     }  
  20.   
  21.     public boolean isRunning() {  
  22.         return running;  
  23.     }  
  24. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值