前面大致提到了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是用来指定两个操作 ...
随机推荐
-
Bash 的 no-fork 优化
我们知道,Bash 在执行一个外部命令时,会先 fork() 一个子进程,然后在子进程里面执行 execve() 去加载那个外部程序.fork 子进程是会耗性能的,所以 Bash 会在下面几种情况下不 ...
-
sql server导出insert语句
在所需要导出数据库上右键 选择[任务] 然后选择[生成脚本] 选择数据库,点击下一步到[数据脚本选项] 编写数据的脚本 选择为true 这一步很重要 下一步选择要导出的对象 下一步选择表 点击完成 ...
-
UITableView出现卡顿如何处理
tableView的beginUpdate和endUpdate要比reloadData和reloadRowsAtIndexPaths好,因为beginUpdate和endUpdate会执行一个动画bl ...
-
MarkDown里面的Emoji表情
我才发现MarkDown里面可以使用一些Emoji表情,好玩,以后写博客的趣味性大大增加 想看全部的就去这里找https://www.webfx.com/tools/emoji-cheat-sheet ...
-
HTML块元素,行内元素,类,头部元素
总结HTML块元素,行内元素,类,头部元素 块元素: 在HTML中,块级元素的高度为其内容的高度,宽度会扩展到与父容器同宽.默认情况下,块级元素会独占一行,并且元素前后行留空. 示例:<h1&g ...
-
《Spring_Four》第三次作业——基于Jsoup的大学生考试信息展示系统的原型设计与开发
<Spring_Four团队>第三次团队项目——基于Jsoup的大学生考试信息展示系统的原型设计与开发 一.实验目的与要求 (1)掌握软件原型开发技术: (2)学习使用软件原型开发工具:本 ...
-
课程5:Spring框架2016版视频--视频列表目录
\day01视频\01-今天内容介绍.avi; \day01视频\02-spring的相关概念.avi; \day01视频\03-spring的ioc底层原理(一).avi; \day01视频\04- ...
-
Java Web 项目简单配置 Spring MVC进行访问
所需要的 jar 包下载地址: https://download.csdn.net/download/qq_35318576/10275163 配置一: 新建 springmvc.xml 并编辑如下内 ...
-
Java之关于JSTL引入问题
错误信息:Can not find the tag library descriptor for “http://java.sun.com/jstl/core”JSTL taglib需要jstl.ja ...
-
5th 各组作品alpha发布体会
1. 俄罗斯方块 武志远 可以进行游戏,界面很友好,游戏运行也很流畅,并找到两名同学现场体验,游戏完成度很好. 2. 连连看游戏 张金生 可以进行游戏,实现了背景音乐播放等附加功能,界面清晰 ...