深入浅出Java并发包—指令重排序
前面大致提到了JDK中的一些个原子类,也提到原子类是并发的基础,更提到所谓的线程安全,其实这些类或者并发包中的这么一些类,都是为了保证系统在运行时是线程安全的,那到底怎么样才算是线程安全呢?
Java并发与实践一书中提出,当多个线程同时访问一个类的时候,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要做额外的同步以及在调用代码时不需要做其他的协调,这个类的运行仍然是正确的,那么这个类是线程安全的。
很显然只有资源竞争时才会出现线程不安全,而无状态的类将永远是线程安全的。因此我们再做分层结果的时候,Service层可以轻松的使用单例去显示,而展示层和数据层却需要每个单独的线程单独一个对象去处理。
之前说了这么一些原子类,他们都是线程安全的类,原子操作的描述是多个线程执行同一个操作时,其中一个线程要么完全执行完成这个操作,要么根本没有执行任何步骤。
在JDK中,JAVA语言为了维持顺序内部的顺序化语义,也就是为了保证程序的最终运行结果需要和在单线程严格意义的顺序化环境下执行的结果一致,程序指令的执行顺序有可能和代码的顺序不一致,这个过程就称之为指令的重排序。指令重排序的意义在于:JVM能根据处理器的特性,充分利用多级缓存,多核等进行适当的指令重排序,使程序在保证业务运行的同时,充分利用CPU的执行特点,最大的发挥机器的性能!
我们来看一组代码示例:
package com.yhj.concurrent;
/**
* @Described:Happen-Before测试
* @author YHJ create at 2013-4-13 下午05:12:36
* @ClassNmae com.yhj.concurrent.HapenBefore
*/
public class HappenBefore { static int x,y,m,n;//测试用的信号变量 public static void main(String[] args) throws InterruptedException {
int count = 10000;
for(int i=0;i<count;++i){
x=y=m=n=0;
//线程一
Thread one = new Thread(){
public void run() {
m=1;
x=n;
};
};
//线程二
Thread two = new Thread(){
public void run() {
n=1;
y=m;
};
};
//启动并等待线程执行结束
one.start();
two.start();
one.join();
two.join();
//输出结果
System.out.println("index:"+i+" {x:"+x+",y:"+y+"}");
}
}
}
这段代码循环1w次, 每次启动两个线程去修改x、y、m、n四个变量,能得到什么结果的呢?运行一下,很容易得到x=1,y=0;x=0,y=1两种结果,事实上根据JVM规范以及CPU的特性,我们很可能还能得到x=0,y=0或者x=1,y=1的情况。当然上端代码大家不一定能得到x=0,y=0或者x=1,y=1的结果,这是因为这段代码太简单了,以现在CPU 的运算速度,根本无需做线程切换就能将这些很快的执行完毕。x=1,y=1这种情况大家也许还能理解,当发生线程切换时,第一个线程第一行代码执行完毕,再次执行第二线程的第一行代码,就会发生x=1,y=1的结果。但x=0,y=0是否可能发生?按照现在的JVM和CPU特性,这种情况的确是存在的。由于线程的run方法里面的动作对结果是无关的,因此里面的指令可能发生指令重排序,即使是按照程序的顺序方法执行,数据从线程缓冲区刷新到主存也是需要时间的(之前有提到,原理可参考http://yhjhappy234.blog.163.com/blog/static/316328322011101723933875/,实践可通过下面方框中的测试代码验证)。假定是按照m=1,x=n,n=1,y=m执行的,显然x=0是很正常的,m=1虽然在y=m之前执行,但是线程one有可能还没来得及将m=1的数据从高速缓存(work memory)写入主存,线程two就从主存中取m的数据,所以还有可能是0,这样就发生了数据错误!尤其是在大并发和多核CPU的执行下,数据的结果就更无法确定了!
package com.yhj.jvm.memory.concurrent; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* @Described:并发常量测试
* @author YHJ create at 2013-04-17 下午08:54:24
* @FileNmae com.yhj.jvm.memory.concurrent.ConcurrentStaticTest.java
*/
public class ConcurrentStaticTest { public static int counter = 0;//volatile public final static int THRED_COUNT = 20; public static void plus() {
counter++;
} /**
* @param args
* @Author YHJ create at 2011-11-17 下午08:54:19
*/
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0;i<THRED_COUNT;++i){
executorService.execute(new Runnable() { @Override
public void run() {
for(int j = 0;j<10000;++j){
plus();
}
} });
}
//等待所有进程结束
while(Thread.activeCount()>1){
Thread.yield();
}
System.out.println(counter); }
}
为了解决此类额外难题,Java存储模型引入了happens-Before发则,确保并发情况下的数据正确性!通俗的说就是如果动作B要看到动作A的执行结果(无论A/B是否在同一个线程中),那么A/B必须满足happens-before发则!
在说happens-before发则之前我们还得先看另外一个概念:在Java中还有一个概念叫JMMA(Java Memory Medel Action):Java模型动作。一个Action包含:编写读、变量写、监视器加锁、释放锁、线程启动(start)、线程等待(join)。关于锁我们后续会详细介绍。
说了这么多,那究竟什么是happens-before发则呢?完整的发则如下
(1)同一个线程中的每个Action都happens-before于出现在其后的任何一个Action。
(2)对一个监视器的解锁happens-before于每一个后续对同一个监视器的加锁。
(3)对volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作。
(4)Thread.start()的调用会happens-before于启动线程里面的动作。
(5)Thread中的所有动作都happens-before于其他线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false。
(6)一个线程A调用另一个另一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检测到B的isInterrupted()或者interrupted())。
(7)一个对象构造函数的结束happens-before与该对象的finalizer的开始
(8)如果A动作happens-before于B动作,而B动作happens-before与C动作,那么A动作happens-before于C动作。
法则中提到了一个关键字volatile,其实我们前面讲JVM的时候也多次提到这个关键字,今天我们略微扩展一点,因为他对我们后续的CAS理解有很大帮助。
Volatile相当于synchronized的一个弱实现,他实现了synchronized的语义却没有锁机制,它确保对volatile字段的更新以可预见的形式告知其他线程。
Java存储模型不对对olatile指令的操作做重排序,保证volatile的变量都能按照指令的顺序执行。
Volatile类型的变量不会被缓存在寄存器中(寄存器中的数据只有当前线程可以访问),或者其他对CPU不可见的地方,每次都需要充主存中读取对应的数据,这保证每次对变量的修改,其他线程也是可见的,而不是仅仅修改自己线程的局部变量,在happens-before发则中,对一个volatile变量进行写操作后了,此后的任何读操作都可见该次写操作的结果。
Volatile关键字主要用于以下场景
volatile boolean condition = false; public void method() {
while(!condition){
doSth();
}
}
应用volatile关键字的三个发则
(1)写入变量不依赖此变量的值,或者只有一个线程修改此变量
(2)变量的状态不需要与其它变量共同参与不变约束
(3)访问变量不需要加锁
Happens-before和volatile是后面锁和原子操作的基础,那锁操作和原子操作是怎么实现的呢?请参考后续连载的章节!
深入浅出Java并发包—指令重排序的更多相关文章
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
转: http://www.blogjava.net/xylz/archive/2010/07/03/325168.html 在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到 ...
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则[转]
在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念. 在Java Concurrency in Practice中是这样定义线程安全的: ...
- Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
- Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
- java指令重排序的问题
转载自于:http://my.oschina.net/004/blog/222069?fromerr=ER2mp62C 指令重排序是个比较复杂.觉得有些不可思议的问题,同样是先以例子开头(建议大家跑下 ...
- Java并发编程-线程可见性&线程封闭&指令重排序
一.指令重排序 例子如下: public class Visibility1 { public static boolean ready; public static int number; } pu ...
- 【java多线程系列】java内存模型与指令重排序
在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...
- java高并发核心要点|系列4|CPU内存指令重排序(Memory Reordering)
今天,我们来学习另一个重要的概念. CPU内存指令重排序(Memory Reordering) 什么叫重排序? 重排序的背景 我们知道现代CPU的主频越来越高,与cache的交互次数也越来越多.当CP ...
- java并发学习--第九章 指令重排序
一.happns-before happns-before是学习指令重排序前的一个必须了解的知识点,他的作用主要是就是用来判断代码的执行顺序. 1.定义 happens-before是用来指定两个操作 ...
随机推荐
- 如何判断一个js对象是不是Array
1. instance of 2.constructor 3. isArray 1.var a=new Array(); a instanceof Array; //true 2.var a=new ...
- 微信小程序视频地址
微信小程序视频系列教程完整版,课程中用到的源码附在帖子最后. [url=http://bbs.larkapp.com/forum.php?mod=viewthread&tid=5673][b] ...
- js学习笔记一-语法结构
js是区分大小写的,关键字.变量.函数名和所有的标识符都必须采取统一一致的大小写形式. js定义了unicode转义序列,以\u开头,其后跟随四个十六进制数,可以在字符串直接量.正则表达式直接量和标识 ...
- php win主机下实现ISAPI_Rewrite伪静态
有的win主机iss不支持 .htaccess 文件, 我在这里指的不是本地 在本地的话用apmserv服务器可以用.htaccess 文件,用apmserv服务器环境配置伪静态可以看 php 伪静态 ...
- Laravel 5 基础(一)- Laravel入门和新建项目
此系列文章是 laracasts.com 中的入门系列视频的笔记,我做了一些修改,可以参考此系列文章来学习 Laravel 5.原视频作者是 Jeffrey Way, 在此感谢.本人使用的系统是Mac ...
- haproxy 常用acl规则与会话保持
一.常用的acl规则 haproxy的ACL用于实现基于请求报文的首部.响应报文的内容或其它的环境状态信息来做出转发决策,这大大增强了其配置弹性.其配置法则通常分为两 步,首先去定义ACL,即定义一个 ...
- 第25章 项目6:使用CGI进行远程编辑
初次实现 25-1 simple_edit.cgi --简单的网页编辑器 #!D:\Program Files\python27\python.exeimport cgiform = cgi.Fiel ...
- 全民wifi钓鱼来临----agnes安卓wifi钓鱼神器介绍
断断续续搞了一些无线的东西,从bt5的aircrack-ng的破无线(没怎么成功过)其实EWSA这个用GPU跑还算不错,可惜了我这显卡也只能每秒2500,到用c118在OsmocomBB基础上进行gs ...
- wpf MVVM ViewModel 关闭View显示
上次说到了不同wpf窗体之间的交互,这个在MVVM模式之中用起来会方便很多 下面我来说下在ViewModel中关闭View的方法,其实也很简单的,注释照样不写,一看就懂的 public partial ...
- CentOS6.3连网获取IP失败 This device is not active
虚拟机拷贝到其它机器之后,启动:然后用ifconfig -a发现eth0没有IP地址,查看 /etc/sysconfig/network-scripts/ifcfg-eth0文件,发现IP地址已经指定 ...