黑马-----内存模型和volatile详解

黑马程序员:Java培训、Android培训、iOS培训、.Net培训

JAVA线程-内存模型和volatile详解

一、单核内存模型

1、程序运行时,将临时数据存放到Cache中

2、将CPU计算所需要的数据从Cache中拷贝一份到H Cache中

3、CPU直接从H Cache中读取数据进行计算

4、CPU将计算的结果写入H Cache中

5、H Cache将最新的结果值涮入Cache中(何时写入不确定)

6、将Cache中结果数据写回程序(如果有需要,例如文件、数据库)

需要H Cache的原因:CPU的执行速度很快,而向Cache读取或写入数据则相对慢得多,因此,就需要H Cache来弥补。

二、多核内存模型

有2个线程:ThreadA和ThreadB,分别在不同的CUP内运行,并且执行如下代码:

i = 0; i = i + 1;。最后,我们希望i的值为2。

1、ThreadA和ThreadB分别读取i=0的值存入各自的H Cache中,此时H Cache中的i值都为0;ThreadA和ThreadB分别对i进行计算并得到的结果都为1;2个H Cache分别将结果写入Cache,最终,Cache中i的值为1。显然,这不是我们希望得到的结果。这就是著名的:缓存一致性问题。(对单核CPU也会出现同样的问题,只是单核CPU以线程调度的形式来分别执行)

2、缓存一致性问题的解决方法

1)总线加LOCK#锁(效率低下,不可取)

2)缓存一致性协议(这里不详述)

三、并发的三个概念

1、原子性

1)即一个操作或多个操作,要么全部执行并且执行的过程中不会被任何因素打断,要么不执行。

2)有如下代码:i = 9999999999;

假设为一个32位的变量赋值包括2个过程:为低16位赋值,为高16位赋值。现在,可能会发生:ThreadA将低16位数值写入之后,突然被中断,此时,ThreadB读取i的值就会得到错误的结果。

2、可见性

1)是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其它线程能够立即看到修改的值。

2)ThreadA和ThreadB分别执行如下代码:

ThreadA :int i = 0;

i = 10;

ThreadB :int j = i;

(1)当ThreadA执行到i=10时,首先将i的初始值加载到其CPU的H Cache中,然后赋值为10。那么,ThreadA的H Cache中i的值为10,但是,ThreadA却没有立即将H Cache中i的值马上写入Cache中。

(2)此时,ThreadB开始执行,然而Cache中i的值仍然为0,最终,不管怎样,j的值都为0。而不是我们希望j=10那样。

(3)这正是由于ThreadA对i修改后,ThreadB没有立即看到线程ThreadA对i修改的值。

3、有序性

1)即程序执行的顺序按照代码的先后顺序执行。

2)指令重排序:即处理器为了提高程序运行效率,可能会对输入代码进行优化,它不会保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

3)有如下代码:

int a = 10;  //语句1

int r = 2;   //语句2

a = a + 3;  //语句3

r = a * a;  //语句4

可能执行的顺序:1 2 3 4 或 2 1 3 4,不再可能有其它。这是因为处理器需要考虑指令之间的数据依赖性。

4)ThreadA和ThreadB分别执行如下代码:

ThreadA:context = loadContext();  //语句1

inited = ture;           //语句2

ThreadB:while(!inited) sleep();    //语句3

doSomething(context);   //语句4

如果处理器对ThreadA的指令进行重排,则ThreadB可能在context没有赋值的情况下执行doSomething(context),从而导致程序运行错误。

可见,原子性、可见性、有序性都不会影响单个线程对代码的执行结果。但是,会影并发执行的正确性。想要程序正确的并发执行,必须同时保证原子性、可见性、有序性。

四、Java确保并发执行的正确性

1、原子性

1)在Java中,对基本数据类型的变量的读取和赋值操作是原子性的。

2)下面哪些语句是原子操作的

x = 10;    //  是原子操作

x = x + 1;  //  不是:首先读取x值,在加1,最后赋值

x ++;      //  不是:首先读取x值,在加1,最后赋值

y = x;    //   不是:首先读取x值,然后赋值

3)如果实现更大范围的原子性,可使用synchronized和Lock来实现。

2、可见性

1)使用volatile来确保可见性:保证了修改的值会立即被更新到Cache并且使其它线程的H Cache中的volatile变量的值无效。

2)也可使用synchronized和Lock来确保可见性:保证了修改的值在锁被释放之前被更新到Cache并且使其它线程的H Cache中的相关变量的值无效。

3、有序性

1)使用volatile来确保真正的有序性:禁止对volatile变量进行指令重排序。

2)synchronized和Lock也能确保的有序性:以单线程执行同步代码块的方式来实现的。

五、volatile变量的详细论述

1、volatile关键字的两层含义:

1)保证可见性

2)禁止指令重排序,保证有序性

(1)当对volatile变量进行读取或写入操作时,在其前面对该volatile变量的操作早已完成并且结果已对当前操作可见,而当前操作的后序操作肯定还没有执行。

(2)进行指令优化时,不能将操作volatile变量前的指令放到volatile变量后执行,也不能将操作volatile变量后的指令放到volatile变量前执行。

注意:volatile关键字不保证原子性

3)例如:

x = 9;             //语句1

y = 8;             //语句2

volatile flag = ture;  //语句3

i = 7;             //语句4

k = 6;             //语句5

尽管x,y,i,j之间不存在依赖性,但是,语句4和语句5不会被放到语句3之前执行,而语句1和语句2也不会被放到语句3之后执行。

可能执行的顺序有:1,2,3,4,5 或 2,1,3,4,5,或 1,2,3,5,4

或2,1,3,5,4

2、volatile与synchronized的区别

1)共同点

(1)volatile与synchronized都是同步机制的一部分

(2)都实现了可见性和有序性

2)区别

(1)synchronized实现了原子性,而volatile没有

(2)作用的对象不同:volatile作用的是变量,而synchronized作用的是语句块或方法。

(3)对线程的作用不同:volatile不会阻塞线程,而synchronized会阻塞线程,即volatile没有使用监视器,而synchronized使用了监视器。所以,volatile是一种比synchronized更轻量的弱化的同步机制。

六、volatile的正确使用

1、模式一:状态标记

1)没有使用volatile导致的并发问题

ThreadA :boolean stop = false;         ThreadB :stop = ture;

while(!stop){

//A

doSomething();

}

并发可能不会正确执行:即ThreadA进入死循环

原因:1、ThreadA永远只读其H Cache中stop的值

2、ThreadB只将修改了stop的值保存到其H Cache中

3、即使ThreadA偶尔会从Cache中读取stop的值,如果Thread在A处阻

塞,而此时ThreadB修改了stop的值并且写入Cache,由于ThreadA没有看到Cache中stop值已经修改,即使重新执行,也可能会进入死循环。

2)使用volatile解决

ThreadA :boolean stop = false;         ThreadB :stop = ture;

while(!stop){

//A

doSomething();

}

2、模式二:Double-check(在单例模式中的使用)

private volatile static Singleton instance;

public stratic Singleton getInstance(){

if(instance == null){

synchronized(Singleton.class){

if(instance == null){ instance = new Singleton();}

}

}

}

这是volatile与synchronized配合使用的经典案例。

3、模式三:开销较低的读-写锁策略

public class CheesyCounter{

//All mutative operations msut be done with the ‘this’lock held

@GuardedBy(“this”) private volatile int value;

public int getVulue(){return value;}

public synchronized int increment(){ return value++;}

}

1)volatile与synchronized配合使用的另一个经典案例

2)如果读操作远远超过写操作,可以结合使用锁和volatile变量来减少公共代码路径的开销,例如本例。

3)计数器必须使用synchronized来确保增量操作是原子的,同时使用volatile保证当前结果的可见性。

4)如果更新不频繁,读取的开销仅仅涉及volatile操作,这由于一个无竞争锁获取的开销。

网上有评论:本模式中value不使用volatile也能实现ThreadSave,因为increment()

使用了synchronized,真的这样吗?答案是否定的,如果ThreadA正进

行increment(),注意synchronized只实现了对increment()的互斥访问,而没有实现对value的互斥访问,而ThreadB也在进行getVulue(),那么ThreadB将会的到一个失效的value值,因为ThreadB不知道ThreadA正在对value进行修改。

4、模式四:一次性安全发布,发布不可变对象或线程安全的对象

public class Test{

public volatile FinalObject object;

public void CreateObject(){object = new FinalObject(…);} //ThreadA

public void doWork(){                            //ThreadB

while(ture){         //轮询

if(object != null){doSomething(object);}

}

}

}

1)发布:使对象能够在当前线程作用域之外的代码中使用。

2)如果object引用不是一个volatile型,doWork()对object的引用可能得到一个不完全构造的的FinalObject。

3)必须注意:object本身必须是线程安全的。

4)volatile类型确保了发布形式的可见型,但如果object的状态在发布后可变,那么就需要额外的同步。

5)这个案例还展示出:在不使用阻塞的前提下,阻塞另一个线程的执行(尽管这不是真正的阻塞,但起到了阻塞的作用)。

网上评论:volatile不足以实现安全发布,原因在于object在构造过程中可能被中断。我们应当记得volatile有一个很重要的特性:禁止对volatile变量进行指令重排序。即中断指令要么在object在构造前执行,要么在object在构造后执行,不可能出现在构造过程中。

5、模式五:独立观察

public class UserManager{

public volatile String lastUser;

public boolean authenticate(String userName, String password){

boolean valid = passwordIsValid(user, password);

if(valid){

User u = new User(userName, password);

activeUsers.add(u);

lasstUser = user;

}

return valid;

}

………

}

1)本例展示了身份机制如何记忆最近一次登陆的用户的名字,并将反复使用lastUser引用来发布值,以供程序的其它部分使用。

2)该模式的另一个使用是收集程序的统计信息或定期(不定期)发布信息

3)这个模式要求:(1)被发布的值是有效不可变的-即值的状态在发布后不会更改(下一次发布已经是一个新的值,而不是在原有值的基础上的改变)

(2)使用发布值的代码需要清楚该值可能随时发生变化。

6、模式六:volatile-bean模式

@ThreadSafe

public class Person{

private volatile Stirng name;

private volatile int age;

public String getName(){return name;}

public int getAge(){return age;}

public void setName(String name){this.name = name;}

public void setAge(int age){this.age = age;}

}

1)原理:很多框架为易变数据的持有者(例如HttpSession)提供了容器,但是放入这些容器中的对象必须是线程安全的。

2)volatile-bean模式的所有成员都必须是volatile并且有效不可变,同时只能有getter和setter方法。

四、volatile变量的使用原则

1、写入变量不依赖此变量的值,或只有一个线程修改此变量

2、变量不与其它变量共同参与不变约束

3、访问变量不需要加锁

时间: 2024-10-28 11:30:21

黑马-----内存模型和volatile详解的相关文章

Java线程角度的内存模型和volatile型变量

内存模型的目标是定义程序中各个变量的访问 规则,即在虚拟机中将变量(包括实例字段,静态字段和构成数组对象的元素,不包括局部变量与方法参数,因为后者是线程私有的)存储到内存和从内存中取出变量这样的底层细节. Java内存模型规定所有的变量都存储在住内存,每条线程还有自己的工作内存,工作内存保存了被该线程使用到的变量和主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存的变量,不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递需要通过主内存来完成. 关键

深入java内存模型(二) volatile详解

对于volatile修饰符,我们应该不会陌生,在多线程中使用常见,比如多个线程想用一个全局变量作为标识符,或者一个共享变量,我们都会给该变量加上一个volatile的修饰符.volatile用中文解释是易变的,不稳定的.说明该变量会被多个线程访问并可能修改.那么jvm是怎样发挥volatile关键字的作用,如何实现的呢? 上一篇深入java内存模型中解释了jvm中的重排序以及四种内存屏障等.jvm总是会以一些易懂,使用方便的方式来实现相关功能.比如垃圾回收器,对于内存的申请与释放时一个令人头疼的

Java虚拟机内存模型和volatile型变量

Java虚拟机内存模型 了解Java虚拟机的内存模型,有助于我们明白为什么会发生线程安全问题. 上面这幅图是<深入理解Java虚拟机-JVM高级特性与最佳实践>的书中截图. 线程共享的变量会保存在主内存中(Main Memory). 而线程共享的变量的副本会保存在每个线程各自的工作内存中(Working Memory). 线程对于共享变量的所有操作(读取,赋值等)都必须在工作内存中进行,不能直接读写主内存的变量. 不同的线程之间,也无法访问其他线程的工作内存.线程之间的变量传递需要通过主内存来

Java同步内存模型和Volatile关键字

计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度.因此在CPU里面就有了高速缓存. 也就是,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和

并发编程-(3)Java内存模型和volatile

目录 1.内存模型概念 2.多线程的特性 1.1.原子性 1.2.可见性 1.3.有序性 2.Java内存模型 2.1.JMM和JVM 2.2.Java内存模型(JMM) 2.2.1.案例 2.2.2.volatile作用 2.3.重排序 2.3.1.什么是重排序 2.3.2.重排序如何影响线程安全 2.4.总结 1.内存模型概念 我们都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的

BOM浏览器对象模型和API详解

什么是BOMBOM是Browser Object Model的缩写,简称浏览器对象模型BOM提供了独立于内容而与浏览器窗口进行交互的对象由于BOM主要用于管理窗口与窗口之间的通讯,因此其核心对象是windowBOM由一系列相关的对象构成,并且每个对象都提供了很多方法与属性,告诉线程如何操作对象 全局函数: js基于对象,但也有不封装在对象中的函数. 对话框alert(str)—— 弹出消息对话框(对话框中有一个“确定”按钮)confirm(str)—— 弹出消息对话框(对话框中包含一个“确定”按

不止面试02-JVM内存模型面试题详解

第一部分:面试题 本篇文章我们将尝试回答以下问题: 描述一下jvm的内存结构 描述一下jvm的内存模型 谈一下你对常量池的理解 什么情况下会发生栈内存溢出?和内存溢出有什么不同? String str = new String("abc")创建了多少个实例? 第二部分:深入原理 ok,开始.怎们还是先讲原理,再说答案.如果时间不足,也可以直接跳到最后看答案. 本次分享我们主要围绕jvm内存结构展开,这也是java面试必考知识点之一.所以我们先来看看jvm内存结构到底是啥样子. 1. j

Java并发关键字Volatile 详解

Java并发关键字Volatile 详解 问题引出: 1.Volatile是什么? 2.Volatile有哪些特性? 3.Volatile每个特性的底层实现原理是什么? 相关内容补充: 缓存一致性协议:MESI ? 由于计算机储存设备(硬盘等)的读写速度和CPU的计算速度有着几个数量级别的差距,为了不让CPU停下来等待读写,在CPU和存储设备之间加了高速缓存,每个CPU都有自己的高速缓存,而且他们共享同一个主内存区域,当他们都要同步到主内存时,如果每个CPU缓存里的数据都不一样,这时应该以哪个数

Java内存模型和JVM内存管理

Java内存模型和JVM内存管理   一.Java内存模型: 1.主内存和工作内存(即是本地内存): Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取出变量这样的底层细节.此处的变量与Java编程里面的变量有所不同步,它包含了实例字段.静态字段和构成数组对象的元素,但不包含局部变量和方法参数,因为后者是线程私有的,不会共享,当然不存在数据竞争问题(如果局部变量是一个reference引用类型,它引用的对象在Java堆中可被各个线程共享,但是ref