http://blog.csdn.net/liujinwei2005/article/details/6295666

原帖:http://rwl6813021.javaeye.com/blog/349169

研究ThreadPoolExecutor的时候,发现其中大量使用了volatile变量。不知为何,因此做了一番查找,研究: 其中借鉴了很多网上资料。 在了解volatile变量作用前,先需要明白一些概念:

什么是原子操作? 
所谓原子操作,就是"不可中断的一个或一系列操作" , 在确认一个操作是原子的情况下,多线程环境里面,我们可以避免仅仅为保护这个操作在外围加上性能昂贵的锁,甚至借助于原子操作,我们可以实现互斥锁。 很多操作系统都为int类型提供了+-赋值的原子操作版本,比如 NT 提供了 InterlockedExchange 等API, Linux/UNIX也提供了atomic_set 等函数。

关于java中的原子性? 
原子性可以应用于除long和double之外的所有基本类型之上的“简单操作”。对于读取和写入出long double之外的基本类型变量这样的操作,可以保证它们会被当作不可分(原子)的操作来操作。 因为JVM的版本和其它的问题,其它的很多操作就不好说了,比如说++操作在C++中是原子操作,但在Java中就不好说了。 另外,Java提供了AtomicInteger等原子类。再就是用原子性来控制并发比较麻烦,也容易出问题。

volatile原理是什么? 
Java中volatile关键字原义是“不稳定、变化”的意思 
使用volatile和不使用volatile的区别在于JVM内存主存和线程工作内存的同步之上。volatile保证变量在线程工作内存和主存之间一致。 
其实是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我. 
实现原理:

Volatile的实现原理

那么Volatile是如何来保证可见性的呢?在x86处理器下通过工具获取JIT编译器生成的汇编指令来看看对Volatile进行写操作CPU会做什么事情。

Java代码: instance = new Singleton();//instance是volatile变量
汇编代码: 0x01a3de1d: movb $0x0,0x1104800(%esi);0x01a3de24: lock addl $0x0,(%esp);

有volatile变量修饰的共享变量进行写操作的时候会多第二行汇编代码,通过查IA-32架构软件开发者手册可知,lock前缀的指令在多核处理器下会引发了两件事情。

  • 将当前处理器缓存行的数据会写回到系统内存。
  • 这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。

处理器为了提高处理速度,不直接和内存进行通讯,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完之后不知道何时会写到内存,如果对声明了Volatile变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。

这两件事情在IA-32软件开发者架构手册的第三册的多处理器管理章节(第八章)中有详细阐述。

Lock前缀指令会引起处理器缓存回写到内存。Lock前缀指令导致在执行指令期间,声言处理器的 LOCK# 信号。在多处理器环境中,LOCK# 信号确保在声言该信号期间,处理器可以独占使用任何共享内存。(因为它会锁住总线,导致其他CPU不能访问总线,不能访问总线就意味着不能访问系统内存),但是在最近的处理器里,LOCK#信号一般不锁总线,而是锁缓存,毕竟锁总线开销比较大。在8.1.4章节有详细说明锁定操作对处理器缓存的影响,对于Intel486和Pentium处理器,在锁操作时,总是在总线上声言LOCK#信号。但在P6和最近的处理器中,如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号。相反地,它会锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为“缓存锁定”,缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据

一个处理器的缓存回写到内存会导致其他处理器的缓存无效。IA-32处理器和Intel 64处理器使用MESI(修改,独占,共享,无效)控制协议去维护内部缓存和其他处理器缓存的一致性。在多核处理器系统中进行操作的时候,IA-32 和Intel 64处理器能嗅探其他处理器访问系统内存和它们的内部缓存。它们使用嗅探技术保证它的内部缓存,系统内存和其他处理器的缓存的数据在总线上保持一致。例如在Pentium和P6 family处理器中,如果通过嗅探一个处理器来检测其他处理器打算写内存地址,而这个地址当前处理共享状态,那么正在嗅探的处理器将无效它的缓存行,在下次访问相同内存地址时,强制执行缓存行填充。

接下来是测试 :(通过测试能更好的发现和分析问题) 
申明了几种整形的变量,开启100个线程同时对这些变量进行++操作,发现结果差异很大: 
>>Execute End: 
>>Atomic: 100000 
>>VInteger: 38790 
>>Integer: 68749 
>>Source i: 99205 
>>Source Vi: 99286 
也就是说除了Atomic,其他的都是错误的。

我们通过一些疑问,来解释一下。

1:为什么会产生错误的数据? 
多线程引起的,因为对于多线程同时操作一个整型变量在大并发操作的情况下无法做到同步,而Atom提供了很多针对此类线程安全问题的解决方案,因此解决了同时读写操作的问题。

2:为什么会造成同步问题? 
Java多线程在对变量进行操作的时候,实际上是每个线程会单独分配一个针对i值的拷贝(独立内存区域),但是申明的i值确是在主内存区域中,当对i值修改完毕后,线程会将自己内存区域块中的i值拷贝到主内存区域中,因此有可能每个线程拿到的i值是不一样的,从而出现了同步问题。

3:为什么使用volatile修饰integer变量后,还是不行? 
因为volatile仅仅只是解决了存储的问题,即i值只是保留在了一个内存区域中,但是i++这个操作,涉及到获取i值、修改i值、存储i值(i=i+1),这里的volatile只是解决了存储i值得问题,至于获取和修改i值,确是没有做到同步。

4:既然不能做到同步,那为什么还要用volatile这种修饰符? 
主要的一个原因是方便,因为只需添加一个修饰符即可,而无需做对象加锁、解锁这么麻烦的操作。但是本人不推荐使用这种机制,因为比较容易出问题(脏数据),而且也保证不了同步。

5:那到底如何解决这样的问题? 
        第一种:采用同步synchronized解决,这样虽然解决了问题,但是也降低了系统的性能。 
        第二种:采用原子性数据Atomic变量,这是从JDK1.5开始才存在的针对原子性的解决方案,这种方案也是目前比较好的解决方案了。

6:Atomic的实现基本原理? 
首先Atomic中的变量是申明为了volatile变量的,这样就保证的变量的存储和读取是一致的,都是来自同一个内存块,然后Atomic提供了getAndIncrement方法,该方法对变量的++操作进行了封装,并提供了compareAndSet方法,来完成对单个变量的加锁和解锁操作,方法中用到了一个UnSafe的对象,现在还不知道这个UnSafe的工作原理(似乎没有公开源代码)。Atomic虽然解决了同步的问题,但是性能上面还是会有所损失,不过影响不大,网上有针对这方面的测试,大概50million的操作对比是250ms : 850ms,对于大部分的高性能应用,应该还是够的了。

package qflag.ucstar.test.thread;   

  

import java.util.concurrent.atomic.AtomicInteger;    
   
/** 
* 测试原子性的同步 
* @author polarbear 2009-3-14 

*/   
public class TestAtomic {    
        
    public static AtomicInteger astom_i = new AtomicInteger();    
        
    public static volatile Integer v_integer_i = 0;    
        
    public static volatile int v_i = 0;    
        
    public static Integer integer_i = 0;    
        
    public static int i = 0;    
        
    public static int endThread = 0;    
        
    public static void main(String[] args) {    
        new TestAtomic().testAtomic();    
     }    
        
    public void testAtomic() {    
            
        for(int i=0; i<100; i++) {    
            new Thread(new IntegerTestThread()).start();    
         }    
            
        try {    
            for(;;) {    
                 Thread.sleep(500);    
                if(TestAtomic.endThread == 100) {    
                     System.out.println(">>Execute End:");    
                     System.out.println(">>Atomic: /t"+TestAtomic.astom_i);    
                     System.out.println(">>VInteger: /t"+TestAtomic.v_integer_i);    
                     System.out.println(">>Integer: /t"+TestAtomic.integer_i);    
                     System.out.println(">>Source i: /t"+TestAtomic.i);    
                     System.out.println(">>Source Vi: /t"+TestAtomic.v_i);    
                    break;    
                 }    
             }    
                
         } catch (Exception e) {    
             e.printStackTrace();    
         }    
     }    
        
}    
class IntegerTestThread implements Runnable {    
    public void run() {    
        int x = 0;    
        while(x<1000) {    
             TestAtomic.astom_i.incrementAndGet();    
             TestAtomic.v_integer_i++;    
             TestAtomic.integer_i++;    
             TestAtomic.i++;    
             TestAtomic.v_i++;    
             x++;    
         }    
         ++TestAtomic.endThread;    //貌似很无敌!难道是原子性的吗? 
     }    
}

-----------------------------------------xx-----------------------------------xx-----------------------------------------------

本人继续补充:

除了TestAtomic.endThread,其他的变量都被忽略了。具体解释可参见注释。

import java.util.concurrent.atomic.AtomicInteger; 
import java.io.*;

/** 
* 测试原子性的同步 
* @author pyc 2009-3-29 

*/ 
public class TestAtomic {    
    public static final int N=10;
    public static final int M=10000;
    public static int perfect_result=M*N;
    public static int endThread = 0; 
    
private PrintWriter out;//将信息输入至文本"out.txt",因为控制台buffer可能不够.

public TestAtomic() throws IOException
{
   out =new PrintWriter(
     new BufferedWriter(
       new FileWriter("out.txt")));

       
    public static void main(String[] args) { 
         try{ 
         new TestAtomic().testAtomic(); 
         }catch(Exception e){
         System.out.println(e.getMessage());
          }
         System.out.println("OK./nStatistical report:");
         System.out.println("Covered by "+(perfect_result-endThread)+" times.");
    }   
       
    public void testAtomic() { 
        Thread[] td=new Thread[N];
        for(int i=0; i<N; i++) {   
        td[i]=new Thread(new IntegerTestThread(i+1));
        }   
        for(int i=0; i<N; i++) {   
        td[i].start();
        out.println((i+1)+" go..") ; //此处如果run()方法代码少,立即可观察到complete完成信息。
        }     
        try { 
        long temp=0; //存放了上次的endTread值。
        int count=1000; //如果temp值超过一千次的重复就可以认为结束程序。
        for(;;) { 
           //Thread.sleep(1); //有可能main线程运行过快,可以调节采样的频率。
           if(TestAtomic.endThread == perfect_result) {   
            out.println("==============/r/nPerfect!/r/n=============="); //完美匹配!
                break; 
            }
            if(temp==TestAtomic.endThread){
               out.println("Equal!!");//有重复,有可能是所有线程运行结束时的重复,也有可能是main线程采样过快。
               count--;//倒计时中。。。
            }
            else {
               temp=TestAtomic.endThread;//给temp赋新值。
               count=1000;//重新设置倒计时。
            }
            out.println("endThread = "+TestAtomic.endThread);//在此处有几率可观察当前的endThread值比上次要少。
            //这是关键之处!
            if(count<=0)
            {
               out.println("/r/nI'll be crazy if I wait for that once again!/r/nFailed, OMG!+_+");
               break;
            }
        }   
        out.close();     
        }catch(Exception e) {   
            e.printStackTrace();   
        }   
    }   
    
    class IntegerTestThread implements Runnable { 
    private int id;
    public IntegerTestThread(int i){
       this.id=i;
    }
    public void run() {   
       int i=M;//充分保证线程重叠运行
       while(i>0){
        try{
         //Thread.sleep((int)(10*Math.random()));//设置睡眠时间,从而尽可能使线程重叠运行。
        }catch(Exception e){
        ++TestAtomic.endThread;//测试该语句的“原子”性。其实做完实验,我们知道,
++i,i++,   i=i+1一样都不能保证原子性。
        //我们可以从最终的endThread值是不是等于M*N得知。
        i--;
       }   
       out.println("************/r/n"+id+" has Completed!/r/n************/r/n") ;
    }   
    } 
}

转载于:https://www.cnblogs.com/prctice/p/4434968.html

Java 中的volitle 关键字相关推荐

  1. 面试季,Java中的static关键字解析

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! static关键字是很多朋友在编写代码和阅读代码时碰到的比较难以理解的一个关键字,也是各大公司的面 ...

  2. Java中的static关键字详解

    ** Java中的static关键字详解 ** 在一个类中定义一个方法为static,即静态的,那就是说无需本类的对象就可以调用此方法.调用一个静态方法就是 "类名.方法名" ,静 ...

  3. Java中的instanceof关键字

    Java中的instanceof关键字 instanceof是Java的一个二元操作符,和==,>,<是同一类东东.由于它是由字母组成的,所以也是Java的保留关键字.它的作用是测试它左边 ...

  4. 浅谈Java中的final关键字

    浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...

  5. 浅析Java中的final关键字

    浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...

  6. 深入理解Java中的final关键字

    深入理解Java中的final关键字 http://www.importnew.com/7553.html Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什 ...

  7. 在Java中使用final关键字可以提高性能吗?

    本文翻译自:Does use of final keyword in Java improve the performance? In Java we see lots of places where ...

  8. Java中的static关键字解析 转载

    原文链接:http://www.cnblogs.com/dolphin0520/p/3799052.html Java中的static关键字解析 static关键字是很多朋友在编写代码和阅读代码时碰到 ...

  9. 如何理解 JAVA 中的 volatile 关键字

    如何理解 JAVA 中的 volatile 关键字 最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂. ...

最新文章

  1. [00028]-[2015-09-23]-[00]-[VC 关于Excel操作的测试]
  2. 小程序协同工作和发布
  3. jvm性能调优 - 08什么情况下对象会被GC
  4. LCA求解的四种模板
  5. java正则表达式提取字符串中的数字
  6. 论文公式编号右对齐_如何编辑处理论文中的公式
  7. Filter体现职责链模式
  8. pytorch如何将训练提速?
  9. JProfiler 使用说明
  10. 安卓天天练练(五)CompoundButton
  11. 关于人群基数MCNN和高斯核
  12. Cortex-M3 (NXP LPC1788)之启动代码分析
  13. Java基础-面向对象进阶-多态包final权限修饰符代码块
  14. springboo集成bboss-elasticsearch实现elasticsearch客户端
  15. jquery-9 京东和酒仙网左侧导航如何实现
  16. agv系统介绍_建设AGV系统
  17. Oracle技巧:如何诊断一些未预料的 Oracle 错误(ORA-NNNN)
  18. js:contains函数
  19. 模拟DVD 实现录入 查看 借出 归还 删除 新增 等功能分层
  20. 辰视冯良炳博士将于ITES机器视觉与机器人创新应用大讲台开讲

热门文章

  1. 新疆师范大学计算机研究生值得读吗,研究生还值得读吗
  2. linux基本命令示例_Linux正常运行时间命令示例
  3. Android P功能
  4. jQuery first()和last()函数示例
  5. scala 主构造器_Scala主构造器深度
  6. [nsis]安装包界面乱码问题
  7. C++基础教程之函数重载,什么是C++函数重载?
  8. Python3基础——字典、其他常用操作
  9. Java数据结构与算法(二) 简单排序
  10. Bootstrap-按钮