JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来创建的。所以,咱们首先必须了解这些概念java
1,原子性编程
原子性是指一个操做是不可中断的。即便是在多个线程一块儿执行的时候,一个操做一旦开始,就不会被其余线程干扰,好比,对于一个静态全局变量int i,两个线程同时对它赋值,线程A给他赋值1,线程B给它赋值为-1。那么无论这2个线程以何种方式、何种步调工做,i的值要么是1,要么是-1。线程A和线程B之间是没有干扰的。这就是原子性的一个特色,不可被中断数组
2,可见性(Visibility)缓存
可见性是指当一个线程修改了某一个共享变量的值,其余线程是否可以当即知道这个修改。显然,对于串行程序来讲,可见性问题是不存在的。由于你在任何一个操做步骤中修改了某个变量,那么在后续的步骤中,读取这个变量的值,必定是修改后的新值。安全
3 有序性(Ordering)多线程
有序性问题多是三个问题中最难理解的了。对于一个线程的执行代码而言,咱们老是习惯地认为代码的执行是从先日后,依次执行的。这么理解也不能说彻底错误,由于就一个线程内而言,确实会表现成这样。可是,在并发时,程序的执行可能就会出现乱序。给人直观的感受就是:写在前面的代码,会在后面执行。听起来有些难以想象,是吗?有序性问题的缘由是由于程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致并发
以上概念均摘自《实战java高并发程序设计》ide
Java内存模型规定全部的变量都是存在主存当中,每一个线程都有本身的工做内存。线程对变量的全部操做都必须在工做内存中进行,而不能直接对主存进行操做。而且每一个线程不能访问其余线程的工做内存。高并发
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不同,指包括了实例字段、静态字段和构成数组对象的元素,可是不包括局部变量与方法参数,后者是线程私有的,不会被共享。性能
Java内存模型中规定了全部的变量都存储在主内存中,每条线程还有本身的工做内存(能够与前面讲的处理器的高速缓存类比),线程的工做内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的全部操做(读取、赋值)都必须在工做内存中进行,而不能直接读写主内存中的变量。不一样线程之间没法直接访问对方工做内存中的变量,线程间变量值的传递均须要在主内存来完成,线程、主内存和工做内存的交互关系以下图所示,和上图很相似。
注意:这里的主内存、工做内存与Java内存区域的Java堆、栈、方法区不是同一层次内存划分,这二者基本上没有关系。
由上面的交互关系可知,关于主内存与工做内存之间的具体交互协议,即一个变量如何从主内存拷贝到工做内存、如何从工做内存同步到主内存之间的实现细节,Java内存模型定义了如下八种操做来完成:
若是要把一个变量从主内存中复制到工做内存,就须要按顺寻地执行read和load操做,若是把变量从工做内存中同步回主内存中,就要按顺序地执行store和write操做。Java内存模型只要求上述两个操做必须按顺序执行,而没有保证必须是连续执行。也就是read和load之间,store和write之间是能够插入其余指令的,如对主内存中的变量a、b进行访问时,可能的顺序是read a,read b,load b, load a。Java内存模型还规定了在执行上述八种基本操做时,必须知足以下规则:
Java 使用了一些特殊的操做或者关键字来申明、告诉虚拟机,在这个地方,要尤为注意,不能随意变更优化目标指令。关键字volatile 就是其中之一。当你用volatile 去申明一个变量时,就等于告诉了虚拟机,这个变量极有可能会被某些程序或者线程修改。为了确保这个变量被修改后,应用程序范围内的全部线程都可以“看到”这个改动,虚拟机就必须采用一些特殊的手段,保证这个变量的可见性等特色。
先来看一个使用场景,以下面一段代码在32位java虚拟机上的运行
public class Test { public static long t = 0; public static class ChangeT implements Runnable { private long to; public ChangeT(long to) { this.to = to; } @Override public void run() { while (true) { Test.t = to; Thread.yield(); } } } public static class ReadT implements Runnable { @Override public void run() { while (true) { long temp = Test.t; if (temp != 111L && temp != -999L && temp != 333L && temp != -444L) { System.out.println(temp); } Thread.yield(); } } } public static void main(String args[]) { new Thread(new ChangeT(111L)).start(); new Thread(new ChangeT(-999L)).start(); new Thread(new ChangeT(333L)).start(); new Thread(new ChangeT(-444L)).start(); new Thread(new ReadT()).start(); } }
能够看出因为long是64位在32位的虚拟机上经过多线程赋值,致使数据的读和写都不是原子性的,于是形成,打印出的数字部分是乱的
-4294966963
4294966297
-4294966963
-4294966963
4294966297
-4294967185
-4294966963
4294966297
...
但当使用 volatile修饰后就没有问题了
public volatile static long t = 0;
同时也须要注意volatile是没法彻底保证一些复合操做的原子性,它并不可以代替锁。
package com.ishop.controller; public class Test { public volatile static int i = 0; public static void main(String[] args) throws InterruptedException { AddThread t1 = new AddThread(); AddThread t2 = new AddThread(); AddThread t3 = new AddThread(); AddThread t4 = new AddThread(); t1.start(); t2.start(); t3.start(); t4.start(); System.out.println("i="+i); } } class AddThread extends Thread { @Override public void run() { for (int a = 0; a < 100000; a++){ Test.i++; } } }
如:这一段代码,输出i老是一个小于100000的值。
对于volatile的特色以及使用场景总结以下:
volatile变量具备2种特性:
volatile语义并不能保证变量的原子性。对任意单个volatile变量的读/写具备原子性,但相似于i++、i–这种复合操做不具备原子性,由于自增运算包括读取i的值、i值增长一、从新赋值3步操做,并不具有原子性。
因为volatile只能保证变量的可见性和屏蔽指令重排序,只有知足下面2条规则时,才能使用volatile来保证并发安全,不然就须要加锁(使用synchronized、lock或者java.util.concurrent中的Atomic原子类)来保证并发中的原子性。
由于须要在本地代码中插入许多内存屏蔽指令在屏蔽特定条件下的重排序,volatile变量的写操做与读操做相比慢一些,可是其性能开销比锁低不少。
参考:《实战java高并发程序设计》
文章部份内容引自:http://blog.csdn.net/u011080472/article/details/51337422