多线程之volatile关键字(五)
开始全文之前,先铺垫一下jvm基础知识以及线程栈:
JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。
接下来说说volatile:
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。如果要深入了解volatile关键字的作用,就必须先来了解一下JVM在运行时候的内存分配过程。在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图描述这写交互!
有了以上知识做铺垫,接下来直接上代码:
package thread; public class VolatiDemo1 { private static volatile boolean flag = true; private static final ThreadLocal <Integer> length = new ThreadLocal <Integer>(){ @Override protected Integer initialValue() { return 60; } }; public static void main(String[] args) { //模拟兔子 new Thread(new Runnable() { int rabbitLength = 0; @Override public void run() { while(flag){ try { Thread.sleep(1000); rabbitLength = rabbitLength + 3; System.out.println("兔子跑了 rabbitLength " + rabbitLength + " 米,还剩下:"+ (length.get() - rabbitLength) +" 米--------------------"); if(rabbitLength == length.get()) { System.out.println("兔子获得第一名"); flag = false; } } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); //模拟乌龟 new Thread(new Runnable() { int tortoiseLength = 0; @Override public void run() { while(flag){ try { Thread.sleep(1000); tortoiseLength = tortoiseLength + 1; System.out.println("乌龟 跑了 tortoiseLength " + tortoiseLength + " 米,还剩下 " + (length.get() -tortoiseLength) +"米"); if(tortoiseLength == length.get()) { System.out.println("乌龟获得第一名"); flag = false; } } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); try { Thread.sleep(8000); flag = false; } catch (InterruptedException e) { e.printStackTrace(); } } }