前面大致提到了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并发包—指令重排序的更多相关文章

  1. 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则

    转: http://www.blogjava.net/xylz/archive/2010/07/03/325168.html 在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到 ...

  2. 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则[转]

    在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念. 在Java Concurrency in Practice中是这样定义线程安全的: ...

  3. Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

  4. Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

  5. java指令重排序的问题

    转载自于:http://my.oschina.net/004/blog/222069?fromerr=ER2mp62C 指令重排序是个比较复杂.觉得有些不可思议的问题,同样是先以例子开头(建议大家跑下 ...

  6. Java并发编程-线程可见性&线程封闭&指令重排序

    一.指令重排序 例子如下: public class Visibility1 { public static boolean ready; public static int number; } pu ...

  7. 【java多线程系列】java内存模型与指令重排序

    在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...

  8. java高并发核心要点|系列4|CPU内存指令重排序(Memory Reordering)

    今天,我们来学习另一个重要的概念. CPU内存指令重排序(Memory Reordering) 什么叫重排序? 重排序的背景 我们知道现代CPU的主频越来越高,与cache的交互次数也越来越多.当CP ...

  9. java并发学习--第九章 指令重排序

    一.happns-before happns-before是学习指令重排序前的一个必须了解的知识点,他的作用主要是就是用来判断代码的执行顺序. 1.定义 happens-before是用来指定两个操作 ...

随机推荐

  1. 【风马一族_git_github】gitGui与github的SSH

    权限校验 首先,您的数据保存在远端服务器一份,服务器需要对您的身份识别.一段RSA加密字符串. 启动GUI,菜单-帮助,[Step1-创建密钥]Generate SSH KEY 步骤一: 步骤二: 步 ...

  2. Linux时间相关函数

    相关文件: /etc/localtime  本地时间二级制文件 /etc/sysconfig/clock  时区配置文件 /usr/share/zoneinfo  存储各个时区的二进制文件 时间修改方 ...

  3. Google Ajax Library API使用方法(JQuery)

    Google Ajax Library API使用方法 1.传统方式: <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7. ...

  4. C#操作Excel基本操作

    /// using Microsoft.Office.Core; using Microsoft.Office.Interop.Excel; using System.IO; using System ...

  5. 基础学习总结(八)--HttpClient

    在Android开发中,Android SDK附带了Apache的HttpClient,它是一个完善的客户端.它提供了对HTTP协议的全面支持,可以使用HttpClient的对象来执行HTTP GET ...

  6. API地图坐标转化(批量转换坐标)

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...

  7. Optimize str2date function

    The job can be any string date format convert to AX date format. so that, Do not need to specify str ...

  8. 使用文件监控对象FileSystemWatcher实现数据同步

    最近在项目中有这么个需求,就是得去实时获取某个在无规律改变的文本文件中的内容.首先想到的是用程序定期去访问这个文件,因为对实时性要求很高,间隔不能超过1S,而且每次获取到文本内容都要去分发给WEB服务 ...

  9. Java从入门到精通——基础篇之Servlet与JSP的区别

    一.基本概念 1.1 Servlet Servlet是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面.它担当客户请求(Web浏览器或其他HTTP客户程序)与服务器 ...

  10. Linux nmon 监控工具使用

    Linux 系统下监控指标及指标查看 一.工具介绍     Linux 系统下资源监控使用nmon 工具.它可以帮助在一个屏幕上显示所有重要的性能优化信息,并动态地对其进行更新且并不会消耗大量的CPU ...