JMM和happens-before原则
JMM:
Java Memory Model(Java内存模型),围绕着在并发过程中如何处理可见性、原子性、有序性这三个特性而建立的模型。
可见性:
JMM提供了volatile变量定义、final、synchronized块来保证可见性。
例如:线程a在将共享变量x=1写入主内存的时候,如何保证线程b读取共享变量x的值为1,这就是JMM做的事情。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
原子性:
JMM提供保证了访问基本数据类型的原子性(其实在写一个工作内存变量到主内存是分主要两步:store、write),但是实际业务处理场景往往是需要更大的范围的原子性保证,所以模型也提供了synchronized块来保证。
有序性:
这个概念是相对而言的,如果在本线程内,所有的操作都是有序的,如果在一个线程观察另一个线程,所有的操作都是无序的,前句是“线程内表现为串行行为”,后句是“指令的重排序”和“工作内存和主内存同步延迟”现象,模型提供了volatile和synchronized来保证线程之间操作的有序性。
重排序:
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序(编译器、处理器),就是因为这些重排序,所以可能会导致多线程程序出现内存可见性问题(数据安全问题)和有序性问题。
JMM是如何处理的呢?
对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序
对于处理器重排序,JMM的处理器重排序规则会要求java编译器在生成指令序列时,插入特定类型的内存屏障(memory barriers,intel称之为memory fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序
总之一句话,JMM是通过禁止特定类型的编译器重排序和处理器重排序来为程序员提供一致的内存可见性保证。
A线程具体什么时候刷新共享数据到主内存是不确定的,假设我们使用了同步原语(synchronized,volatile和final),那么刷新的时间是确定的,例如:线程A释放锁后会同步到主内存,线程B获取锁后会同步主内存数据,即“A线程释放锁--B线程获取锁”可以实现A,B线程之间的通信。
java中volatile关键字的含义
http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
Java内存模型happens-before法则
The rules for happens-before are:
Program order rule. Each action in a thread happens-before every action in that thread that comes later in the program order.
Monitor lock rule. An unlock on a monitor lock happens-before every subsequent lock on that same monitor lock.
Volatile variable rule. A write to a volatile field happens-before every subsequent read of that same field.
Thread start rule. A call to Thread.start on a thread happens-before every action in the started thread.
Thread termination rule. Any action in a thread happens-before any other thread detects that thread has terminated, either by successfully return from Thread.join or by Thread.isAlive returning false.
Interruption rule. A thread calling interrupt on another thread happens-before the interrupted thread detects the interrupt (either by having InterruptedException tHRown, or invoking isInterrupted or interrupted).
Finalizer rule. The end of a constructor for an object happens-before the start of the finalizer for that object.
Transitivity. If A happens-before B, and B happens-before C, then A happens-before C.
----------------------------
什么是happens-before?
happens-before就是“什么什么一定在什么什么之前运行”,也就是保证顺序性。
因为CPU是可以不按我们写代码的顺序执行内存的存取过程的,也就是指令会乱序或并行运行,
只有上面的happens-before所规定的情况下,才保证顺序性。
为什么存在可见性问题?
简单介绍下。相对于内存,CPU的速度是极高的,如果CPU需要存取数据时都直接与内存打交道,在存取过程中,CPU将一直空闲,这是一种极大的浪费,妈妈说,浪费是不好的,所以,现代的CPU里都有很多寄存器,多级cache,他们比内存的存取速度高多了。某个线程执行时,内存中的一份数据,会存在于该线程的工作存储中(working memory,是cache和寄存器的一个抽象,这个解释源于《Concurrent Programming in Java: Design Principles and Patterns, Second Edition》§2.2.7,原文:Every thread is defined to have a working memory (an abstraction of caches and registers) in which to store values. 有不少人觉得working memory是内存的某个部分,这可能是有些译作将working memory译为工作内存的缘故,为避免混淆,这里称其为工作存储,每个线程都有自己的工作存储),并在某个特定时候回写到内存。单线程时,这没有问题,如果是多线程要同时访问同一个变量呢?内存中一个变量会存在于多个工作存储中,线程1修改了变量a的值什么时候对线程2可见?此外,编译器或运行时为了效率可以在允许的时候对指令进行重排序,重排序后的执行顺序就与代码不一致了,这样线程2读取某个变量的时候线程1可能还没有进行写入操作呢,虽然代码顺序上写操作是在前面的。这就是可见性问题的由来。
并且,多个CPU之间的缓存也不保证实时同步,
也就是说你刚给一个变量赋值,另一个线程立即获取它的值,可能拿到的却是旧值(或null),
因为两个线程在不同的CPU执行,它们看到的缓存值不一样,
只有在synchronized或volatile或final的性况下才能保证正确性,
很多人用synchronized时只记得有lock的功能,而忘记了线程间的可见性问题。
public class Test { private int n; public void set(int n) {
this.n = n;
} public void check() {
if (n != n)
throw new Exception("check Error!");
}
}
check()中的 n != n 好像永远不会成立,因为他们指向同一个值,但非同步时却很有可能发生。
另外,JMM不保证创建过程的原子性,读写并发时,可能看到不完整的对象,
这也是为什么单例模式中著名的"双重检查成例"方法,在Java中行不通。(但.Net的内存模型保证这一点)
当然,在Java中单例的延迟加载可以用另一种方案实现(方案四):
方案一:非延迟加载单例类
public class Singleton { private Singleton(){} private static final Singleton instance = new Singleton(); public static Singleton getInstance() {
return instance;
}
}
方案二:简单的同步延迟加载
public class Singleton { private static Singleton instance = null; public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
} }
方案三:双重检查成例延迟加载
目的是避开过多的同步,
但在Java中行不通,因为同步块外面的if (instance == null)可能看到已存在,但不完整的实例。
JDK5.0以后版本若instance为volatile则可行
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最新的值。volatile很容易被误用,用来进行原子性操作。
public class Singleton { private static Singleton instance = null; public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
} }
方案四:类加载器延迟加载
public class Singleton { private static class Holder {
static final Singleton instance = new Singleton();
} public static Singleton getInstance() {
return Holder.instance;
} }
JMM和happens-before原则的更多相关文章
- JMM中的Happens-Before原则
在java内存模型中,happens-before应该理解为:前一个操作的结果,可以被后续的操作获取,即内存可见性. 为了解决多线程的内存可见性问题,就提出了happens-before原则, ...
- 全面理解Java内存模型(JMM)及volatile关键字(转载)
关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...
- 全面理解Java内存模型(JMM)及volatile关键字
[版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72772461 出自[zejian ...
- 全面理解Java内存模型(JMM)
理解Java内存区域与Java内存模型Java内存区域 Java虚拟机在运行程序时会把其自动管理的内存划分为以上几个区域,每个区域都有的用途以及创建销毁的时机,其中蓝色部分代表的是所有线程共享的数据区 ...
- 全面理解Java内存模型(JMM)及volatile关键字(转)
原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...
- Java内存模型之happens-before原则
我们无法就所有场景来规定某个线程修改的变量何时对其他线程可见,但是我们可以指定某些规则,这规则就是happens-before,从JDK 5 开始,JMM就使用happens-before的概念来阐述 ...
- 【死磕Java并发】-----Java内存模型之happend-before
在上篇博客([死磕Java并发]-–深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性的问题.那么我们正确使用同步.锁的情况下 ...
- 【死磕Java并发】-----Java内存模型之happens-before
在上篇博客([死磕Java并发]-–深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性的问题.那么我们正确使用同步.锁的情况下 ...
- java内存模型详解
对于本篇文章,将从四个概念来介绍:内存模型基础,重排序,顺序一致性和happens-before 1.内存模型基础 在并发编程中,有两个关键问题:线程之间如何通信和如何同步.由此而引出了两种并发模型: ...
- Java中的公平锁和非公平锁实现详解
前言 Java语言中有许多原生线程安全的数据结构,比如ArrayBlockingQueue.CopyOnWriteArrayList.LinkedBlockingQueue,它们线程安全的实现方式并非 ...
随机推荐
- 不可变数组NSArray
//数组里面不允许存放基本数据类型,只能存放“对象” NSArray *array = [NSArray arrayWithObjects:@"周星星",@"尹天仇&qu ...
- List、Map、Set三个接口,存取元素时,各有什么特点?
List 以特定次序来持有元素,可有重复元素:Set 无法拥有重复元素,内部排序(无序):Map 保存key-value值,value可多值.
- HDU--航海舰队
海舰队 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submiss ...
- Xcode下搭建OpenGL开发环境
#include <GLUT/GLUT.h> void display() { glClear(GL_ENABLE_BIT); glBegin(GL_POLYGON); glVertex2 ...
- (转载)H.264码流的RTP封包说明
H.264的NALU,RTP封包说明(转自牛人) 2010-06-30 16:28 H.264 RTP payload 格式 H.264 视频 RTP 负载格式 1. 网络抽象层单元类型 (NALU) ...
- java 多线程 继承Thread和实现Runnable的区别
1)继承Thread: public class ThreadTest extends Thread { private int count; private String name; public ...
- HDFS Client 设计实现解析
前面对 HDFS NameNode 和 DataNode 的架构设计实现要点做了介绍,本文对 HDFS 最后一个主要构成组件 Client 做进一步解析. 流式读取 HDFS Client 为客户端应 ...
- .Net 跨平台可移植类库正在进行
[原文发表地址] Cross-Platform Portable Class Libraries with .NET are Happening [译文发表地址] .Net 跨平台可移植类库正在进行 ...
- EF遇到的一些问题
环境:EntityFramework 版本号:4.1.0.0 问题一:“数据读取器与指定的“.......”不兼容.某个类型为“...”的成员在同名的数据读取器中没有对应的列.”. 使用方式:rep. ...
- UI控件(UITextView)
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //UITextView与UITextField主要 ...