用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最新的值。volatile很容易被误用,用来进行原子性操做。java
package com.guangshan.test; public class TestVolatile { public static int count = 0; public static void inc () { try { Thread.sleep(1); } catch (Exception e) { } count++; } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { public void run() { TestVolatile.inc(); } }).start(); } System.out.println(count); Thread.sleep(1000); System.out.println(count); } }
这段代码,最后的count值颇有可能不为1000(main函数所在的线程为主线程,主线程的最后一句代码执行后,会进入Thread.exit()方法,该方法会强制终止全部该线程建立的线程),在sleep(1000)后,其余加的线程已经结束了,按理讲,这里的count应该为1000的,可是为何不是1000呢?程序员
不少人觉得,这个是多线程并发问题,只须要在变量count以前加上
volatile
就能够避免这个问题,那咱们在修改代码看看,看看结果是否是符合咱们的指望。
数组
加入volatile以后,仍然有可能不是1000,下面咱们分析一下缘由缓存
在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先经过对象的引用找到对应在堆内存的变量的值,而后把堆内存变量的具体值load到线程本地内存中,创建一个变量副本,以后线程就再也不和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完以后的某一个时刻(线程退出以前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图描述这写交互安全
read and load 从主存复制变量到当前工做内存
use and assign 执行代码,改变共享变量值
store and write 用工做内存数据刷新主存相关内容多线程
其中use and assign 能够屡次出现并发
可是这一些操做并非原子性,也就是 在read load以后,若是主内存count变量发生修改以后,线程工做内存中的值因为已经加载,不会产生对应的变化,因此计算出来的结果会和预期不同jvm
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工做内存的值是最新的函数
例如假如线程1,线程2 在进行read,load 操做中,发现主内存中count的值都是5,那么都会加载这个最新的值优化
在线程1堆count进行修改以后,会write到主内存中,主内存中的count变量就会变为6
线程2因为已经进行read,load操做,在进行运算以后,也会更新主内存count的变量值为6
致使两个线程即便用volatile关键字修改以后,仍是会存在并发的状况。
synchronize关键字修饰的代码块,会自动与主内存同步资源,即在退出sync代码块时,主内存资源自动同步为最新的资源(猜想)
单例的DCL(双重检查加锁)
public class SingletonKerriganD { /** * 单例对象实例 */ private static SingletonKerriganD instance = null; public static SingletonKerriganD getInstance() { if (instance == null) { synchronized (SingletonKerriganD.class) { if (instance == null) { instance = new SingletonKerriganD(); } } } return instance; } }
看起来这样已经达到了咱们的要求,除了第一次建立对象以外,其余的访问在第一个if中就返回了,所以不会走到同步块中。已经完美了吗?
咱们来看看这个场景:假设线程一执行到instance = new SingletonKerriganD()这句,这里看起来是一句话,但实际上它并非一个原子操做(原子操做的意思就是这条语句要么就被执行完,要么就没有被执行过,不能出现执行了一半这种情形)。事实上高级语言里面非原子操做有不少,咱们只要看看这句话被编译后在JVM执行的对应汇编代码就发现,这句话被编译成8条汇编指令,大体作了3件事情:
1.给Kerrigan的实例分配内存。
2.初始化Kerrigan的构造器
3.将instance对象指向分配的内存空间(注意到这步instance就非null了)。
可是,因为Java编译器容许处理器乱序执行(out-of-order),以及JDK1.5以前JMM(Java Memory Medel)中Cache、寄存器到主内存回写顺序的规定,上面的第二点和第三点的顺序是没法保证的,也就是说,执行顺序多是1-2-3也多是1-3-2,若是是后者,而且在3执行完毕、2未执行以前,被切换到线程二上,这时候instance由于已经在线程一内执行过了第三点,instance已是非空了,因此线程二直接拿走instance,而后使用,而后瓜熟蒂落地报错,并且这种难以跟踪难以重现的错误估计调试上一星期都未必能找得出来,真是一茶几的杯具啊。
DCL的写法来实现单例是不少技术书、教科书(包括基于JDK1.4之前版本的书籍)上推荐的写法,其实是不彻底正确的。的确在一些语言(譬如C语言)上DCL是可行的,取决因而否能保证二、3步的顺序。在JDK1.5以后,官方已经注意到这种问题,所以调整了JMM、具体化了volatile关键字,所以若是JDK是1.5或以后的版本,只须要将instance的定义改为“private volatile static SingletonKerriganD instance = null;”就能够保证每次取instance都从主内存读取,就可使用DCL的写法来完成单例模式。
2、如下来自http://rainyear.iteye.com/blog/1734311
线程、工做内存、主内存三者之间的交互关系图:
key edeas
线程的working memory是cpu的寄存器和高速缓存的抽象描述:如今的计算机,cpu在计算的时候,并不老是从内存读取数据,它的数据读取顺序优先级 是:寄存器-高速缓存-内存。线程耗费的是CPU,线程计算的时候,原始的数据来自内存,在计算过程当中,有些数据可能被频繁读取,这些数据被存储在寄存器和高速缓存中,当线程计算完后,这些缓存的数据在适当的时候应该写回内存。当多个线程同时读写某个内存数据时,就会产生多线程并发问题,涉及到三个特 性:原子性,有序性,可见性。 支持多线程的平台都会面临 这种问题,运行在多线程平台上支持多线程的语言应该提供解决该问题的方案。
JVM是一个虚拟的计算机,它也会面临多线程并发问题,java程序运行在java虚拟机平台上,java程序员不可能直接去控制底层线程对寄存器高速缓存内存之间的同步,那么java从语法层面,应该给开发人员提供一种解决方案,这个方案就是诸如 synchronized, volatile,锁机制(如同步块,就绪队 列,阻塞队列)等等。这些方案只是语法层面的,但咱们要从本质上去理解它;
每一个线程都有本身的执行空间(即工做内存),线程执行的时候用到某变量,首先要将变量从主内存拷贝的本身的工做内存空间,而后对变量进行操做:读取,修改,赋值等,这些均在工做内存完成,操做完成后再将变量写回主内存;
各个线程都从主内存中获取数据,线程之间数据是不可见的;打个比方:主内存变量A原始值为1,线程1从主内存取出变量A,修改A的值为2,在线程1未将变量A写回主内存的时候,线程2拿到变量A的值仍然为1;
这便引出“可见性”的概念:当一个共享变量在多个线程的工做内存中都有副本时,若是一个线程修改了这个共享变量的副本值,那么其余线程应该可以看到这个被修改后的值,这就是多线程的可见性问题。
普通变量状况:如线程A修改了一个普通变量的值,而后向主内存进行写回,另一条线程B在线程A回写完成了以后再从主内存进行读取操做,新变量的值才会对线程B可见;
如何保证线程安全
编写线程安全的代码,本质上就是管理对状态(state)的访问,并且一般都是共享的、可变的状态。这里的状态就是对象的变量(静态变量和实例变量)
线程安全的前提是该变量是否被多个线程访问, 保证对象的线程安全性须要使用同步来协调对其可变状态的访问;如果作不到这一点,就会致使脏数据和其余不可预期的后果。不管什么时候,只要有多于一个的线程访问给定的状态变量,并且其中某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问。Java中首要的同步机制是synchronized关键字,它提供了独占锁。除此以外,术语“同步”还包括volatile变量,显示锁和原子变量的使用。
在没有正确同步的状况下,若是多个线程访问了同一个变量,你的程序就存在隐患。有3种方法修复它:
l 不要跨线程共享变量;
l 使状态变量为不可变的;或者
l 在任何访问状态变量的时候使用同步。
volatile要求程序对变量的每次修改,都写回主内存,这样便对其它线程课件,解决了可见性的问题,可是不能保证数据的一致性;特别注意:原子操做:根据Java规范,对于基本类型的赋值或者返回值操做,是原子操做。但这里的基本数据类型不包括long和double, 由于JVM看到的基本存储单位是32位,而long 和double都要用64位来表示。因此没法在一个时钟周期内完成
通俗的讲一个对象的状态就是它的数据,存储在状态变量中,好比实例域或者静态域;不管什么时候,只要多于一个的线程访问给定的状态变量。并且其中某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问;
同步锁:每一个JAVA对象都有且只有一个同步锁,在任什么时候刻,最多只容许一个线程拥有这把锁。
当一个线程试图访问带有synchronized(this)标记的代码块时,必须得到 this关键字引用的对象的锁,在如下的两种状况下,本线程有着不一样的命运。
一、 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有不少的线程,等到其余的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,而且转到就绪状态。
二、 假如这个锁没有被其余线程占用,本线程会得到这把锁,开始执行同步代码块。
(通常状况下在执行同步代码块时不会释放同步锁,但也有特殊状况会释放对象锁
如在执行同步代码块时,遇到异常而致使线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中)
Synchronized关键字保证了数据读写一致和可见性等问题,可是他是一种阻塞的线程控制方法,在关键字使用期间,全部其余线程不能使用此变量,这就引出了一种叫作非阻塞同步的控制线程安全的需求;
ThreadLocal 解析
顾名思义它是local variable(线程局部变量)。它的功用很是简单,就是为每个使用该变量的线程都提供一个变量值的副本,是每个线程均可以独立地改变本身的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每个线程都彻底拥有该变量。
每一个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的而且 ThreadLocal 实例是可访问的;在线程消失以后,其线程局部实例的全部副本都会被垃圾回收(除非存在对这些副本的其余引用)。
http://blog.csdn.net/jinyongqing/article/details/21343629