`
yuwenlin2008
  • 浏览: 127525 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java并发编程:Java内存模型

阅读更多
学习Java并发编程,必须要学习Java内存模型,也是学习和理解后面更深入的课程打下基础,做好准备。今天我们就来学习下Java内存模型。
以下是本文包含的知识点:
1.硬件的效率与一致性
2.Java内存模型
3.主内存和工作内存
4.原子性、可见性与有序性
5.先行发生原则(Happens-before)
一、硬件的效率与一致性
由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之前的缓冲:将运算需要用到的数据复制到缓存中,让运算能快速运行,当运算结束再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了。
基于高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也引入了一个新的问题:缓存一致性。当多个处理器的运算任务都涉及到同一块主内存区域时,将可能导致各自的缓存数据不一致,那同步回到主内存时以谁的缓存数据为准呢?为了解决一致性的问题,需要各处理器访问缓存时都遵循一些协议,在读写时要根据协议来操作,这里说的协议就是内存模型的抽象。Java虚拟机也有自己的内存模型。

 二、Java内存模型
在JDK1.5发布后,Java虚拟机规范中定义的Java内存模型(Java Memory Model JMM)已经成熟和完善。它屏蔽掉各种硬件和操作系统内存的访问差异,以实现让Java程序在各种平台下都能达到一致的访问效果。
三、主内存和工作内存
Java内存模型规定所有变量都存储在主内存(Main Memory)中(可以理解为物理内存,不过是虚拟机内存的一部分),每条线程还有自己的工作内存(Woring Memory,可以理解为前面讲的高速缓存),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值)都必须在工作内存中完成。
如多线程执行变量i++操作的流程:
1).先将变量i读取到工作内存中;
2).然后在工作内存中将i+1;
3).最后将变量i同步到主内存中。

 四、原子性、可见性与有序性
Java内存模型的三大特征:原子性、可见性与有序性
1.原子性(Atomicity):即一个操作要么全部执行并且执行的过程中不被任何因素打断,要不都不执行。
如多线程执行i++操作
public class Test implements Runnable{
     int i = 0;
     public void run(){
          i++;
     }
}
 假如i初始值为0,线程1和线程各自执行一次+1操作,结果是我们想要的2吗?不一定
根据前面讲的内存模型,假如线程1将i=0读取的工作内存中,并对i+1,此时i=1,但只是在线程1的工作内存中,并未同步到主存中。
此时线程2从主存读取i还是=0,并对i+1变为1,此时i=1,但只是在线程1的工作内存中,并未同步到主存中。
然后线程1同步到主存中,最后线程2同步到主存中,程序执行完毕,i的值为1。
这种情况就是原子性操作被打断了。哪如何保证原子性不被打断呢,Java提供了两种方式Lock和synchronized,后续再讲。
 
2.可见性(Visibility)是指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性,无论是普通变量还是volatile变量都是如此,只不过volatile特殊规则保证了新值能够立即同步到主内存,以及每次使用前都从主内存刷新。因此,可以说volatitle保证了变量的可见性,而普通变量不可以。
除了volatitle之外,java还有两个关键字保证可见性,即synchronized和final。
同步的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中”这条规则获得。
而final关键字的可见性是指,被 final修饰的字段一旦在构造器中初始化完成,并且构造器没有把“this”引用传递出去,那在其它线程中就能看见final字段的值。
public static final int i;
public final int j;
static{
     i = 0;
}
{
     //也可以在构造器初始化
     j = 0;
}
 上面代码变量i和j都具有可见性,它们无须同步就能被其它线程正确访问到。
 
3.有序性(Ordering)即程序执行代码的先后顺序。
看下面的代码:
int i=0;
boolean flag = false;
i = 1;                    //语句1
flag = true;          //语句2
 JVM在真正执行这段代码时会按照先语句1、后语句2的顺序来执行吗,不一定,因为这里可能会发生指令重排序。
指令重排序:一般来说,处理器为了提高运行效率,会对运行的代码优化排序,它不保证各个语句的执行顺序与代码的先后顺序一致,但是它会保证程序的执行结果与顺序执行的结果一致。
那它是怎么来保证执行结果一致的呢,原因是它会考虑数据的依赖性。如果指令2必须要用到指令1的结果,那么处理器会保证指令1比指令2先执行。
指令重排序在单线程中是没有问题的,那在多线程中呢,就不一定了。看下面代码:
Map config;
boolean flag = false;
//假设线程1执行如下代码
config = initConfig();//初始化config
flag = true;
//假设线程2执行如下代码
while(!flag){//等待flag为true,代表线程1已经初始化完成
     sleep();
}
//使用线程1初始化好的配置
doSomethingWithConfig();
 假如线程1在执行时,发生指令重排序,先执行了flag = true,后执行initConig(),因为两句没有依赖关系,是可以发生的。
然后线程2再执行时,就发生异常了,因为这时config根本没有初始化完成。
所以指令重排序不会影响单个线程的执行,但是会影响多线程并发执行的正确性。
Java语言提供volatitle和synchronized来保证线程之间操作的有序性。
volatitle本身就包含禁止指令重排序的语义,详细的后面再讲。
synchronized则是由“一个变量在同一个时刻只允许一个线程对其进行Lock操作”这条规则获得。
 
介绍完Java内存模型的三大特征,会不会觉得synchronized是万能的,的确大部分的并发控制操作都能使用synchronized来完成,但就是因为它的万能造就了程序员的滥用,越万能的并发控制,通常会带来越严重的性能影响。后面会讲到虚拟机锁的优化。
 
五、先行发生原则(Happens-before)
如果Java内存模型的所有有序性都靠volatitle和synchronized来完成,那么有一些操作会变得很烦锁,但是我们在编写java并发代码时并没感觉到这一点,这是因为java语言有一个“先行发生”(happens-before) 原则。这个原则非常重要,这是判断数据是否竞争,线程是否安全的重要依据。
下面的Java内存模型下一些天然的发生关系,这些发生关系无须任何同步器,就已经存在,可以在编码中直接使用。如果有两个操作关系不在此列,并且无法从下列关系推导出来,它们就没有顺序保障,虚拟机可以对它们随意重排序。
1)程序次序规则:在一个线程内,按照程序代码顺序或控制流,书写在前在的操作先行发生于写在后面的操作。
2)管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调是同一个对象锁,而后面,指的是时间上的先后顺序。
3)volatitle变量规则:对一个volatitle变量的写操作先行发生于后面对这个变量的读操作。
4)线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
5)线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测。我们可以通过join()方法结束、isAlive()返回值检测线程的终止。
6)线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。可以通过interrupted()检测是否有中断发生。
7)对象终结规则:一个对象的初始化完成(构造方法执行结束)先行发生于它的finalize()方法的开始。
8)传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那么可以得出操作A先行发生于操作C的结论。
我们来看个例子:
private int value = 0;
public void setValue(int value){
     this.value = vlaue;
}
public int getValue(){
     return value;
}
 假设存在线程A和线程B,线程A先(时间上的先后)调用setValue(1),然后线程B调用同一个对象的getValue(),会得到什么结果呢?
我们依次分析下先行发生原则里的各项规则,
由于两个方法分别由线程A和线程B调用,不在一个线程中,所以程序次序规则在这里不适用。
由于没有同步块,自然没有lock与unlock,所以管程锁定规则在这里不适用。
由于变量value没有用volatitle修饰,所以volatitle变量规则在这里不适用。
后面的线程启动、终止、中断、对象终结都跟这没关系。
因为没有一个适用的先行发生规则,所以传递性也不适用。
所以我们可以判定尽管线程A在操作时间上先于线程B,但是无法确定线程B的返回结果,即这里操作不是线程安全的。
那么怎么修复这个问题呢,很简单,要么给seter/geter 方法加上synchronized关键字,这样就可以使用管程锁定规则,要么把变量value定义为volatitle类型,由于setter方法对value的修改不依赖value的原值,满足volatitle关键字的使用场景,这样就可以使用volatitle变量规则。
通过上面的例子可以得出一个结论:一个操作“时间上的先发生”不代表这个操作先行发生。
所以我们衡量并发安全问题的时候不要受到时间顺序的干扰,一切必须以先行发生原则为准。
 
参考
《深入Java 虚拟机》
  • 大小: 29.8 KB
  • 大小: 47 KB
0
2
分享到:
评论

相关推荐

    Java并发编程:设计原则与模式(第二版)-3

    《Java并发编程:设计原则与模式(第二版)》是一本深入探讨Java多线程编程技术的权威著作。这本书详细阐述了在Java平台中进行高效并发处理的关键概念、设计原则和实用模式。以下是对该书内容的一些核心知识点的概述...

    java并发编程:设计原则与模式.rar

    《Java并发编程:设计原则与模式》是一本深入探讨Java多线程编程的书籍,它涵盖了并发编程中的关键概念、原则和模式。在Java中,并发处理是优化应用程序性能、提高资源利用率的重要手段,尤其在现代多核处理器的环境...

    Java并发编程:设计原则与模式2中文版

    《Java并发编程:设计原则与模式2中文版》是一本深度探讨Java开发中并发编程的专著,旨在帮助开发者理解和掌握在多线程环境下编写高效、安全、可维护的代码。这本书涵盖了Java并发编程的核心概念、最佳实践以及常用...

    Java并发编程:设计原则与模式(第二版)-3PDF

    《Java并发编程:设计原则与模式(第二版)》是一本深入探讨Java平台上的多线程和并发编程的权威著作。这本书旨在帮助开发者理解和掌握如何有效地编写可扩展且高效的并发程序。以下是书中涵盖的一些关键知识点: 1....

    《Java并发编程:设计原则与模式(第二版)》

    《Java并发编程:设计原则与模式(第二版)》是一本深入探讨Java多线程编程技术的权威著作。这本书详细阐述了如何在Java环境中高效、安全地进行并发编程,涵盖了多线程设计的关键原则和常见模式。对于Java开发者来说...

    java并发编程实战源码,java并发编程实战pdf,Java

    《Java并发编程实战》是Java并发编程领域的一本经典著作,它深入浅出地介绍了如何在Java平台上进行高效的多线程编程。这本书的源码提供了丰富的示例,可以帮助读者更好地理解书中的理论知识并将其应用到实际项目中。...

    Java 并发编程:设计原则与模式

    本资料“Java并发编程:设计原则与模式”深入探讨了这些关键主题。 首先,我们需要理解Java并发编程的基础概念。Java中的并发是通过线程实现的,线程是程序执行的最小单位。Java提供了多种创建和管理线程的方法,如...

    Java并发编程:设计原则与模式(Concurrent.Programming.in.Java)(中英版)

    《Java并发编程:设计原则与模式》是一本深入探讨Java多线程编程的权威书籍,由Doug Lea撰写,第二版全面涵盖了Java并发处理的各个方面。这本书不仅提供了丰富的理论知识,还介绍了实战中的设计原则和模式,对于Java...

    java 并发编程的艺术pdf清晰完整版 源码

    《Java并发编程的艺术》这本书是Java开发者深入理解并发编程的重要参考书籍。这本书全面地介绍了Java平台上的并发和多线程编程技术,旨在帮助开发者解决在实际工作中遇到的并发问题,提高程序的性能和可伸缩性。 ...

    一本经典的多线程书籍 Java并发编程 设计原则与模式 第二版 (英文原版)

    《Java并发编程 设计原则与模式 第二版》是一本深受程序员喜爱的经典书籍,由Addison Wesley出版。这本书深入探讨了Java平台上的多线程编程技术,为开发者提供了丰富的设计原则和模式,帮助他们理解和解决并发环境中...

    Java 并发编程实战.pdf

    书中会首先介绍Java并发编程的基础知识,包括线程的创建和运行,同步机制的基本用法,以及Java内存模型的相关概念。随着章节的深入,作者可能会更深入地讲解Java提供的并发工具,例如锁、原子变量、线程池、以及并发...

    JAVA并发编程艺术 高清pdf

    JAVA并发编程艺术 高清pdf : 1.并发变成的挑战 2. java并发机制的底层实现原理 3. java 内存模型 4. java并发编程基础 5.java中的锁。。。。。。。

    《java 并发编程实战高清PDF版》

    《Java并发编程实战》是一本深入探讨Java平台并发编程的权威指南。这本书旨在帮助开发者理解和掌握在Java环境中创建高效、可扩展且可靠的多线程应用程序的关键技术和实践。它涵盖了从基本概念到高级主题的广泛内容,...

    java并发编程:设计与模式

    在Java并发编程的学习中,中级开发者通常需要掌握基础的多线程编程技能、了解Java内存模型、熟悉Java并发工具类的使用、能够合理设计线程安全的类和接口,以及能够运用并发设计模式来解决实际问题。 最后,针对给定...

    java并发编程2

    Java并发编程是Java开发中的重要领域,特别是在多核处理器和分布式系统中,高效地利用并发可以极大地提升程序的性能和响应速度。以下是对标题和描述中所提及的几个知识点的详细解释: 1. **线程与并发** - **线程*...

    Java并发编程_设计原则和模式(CHM)

    Java并发编程是软件开发中的重要领域,特别是在多核处理器和分布式系统中,高效地利用并发可以极大地提升程序的性能和响应速度。本资源"Java并发编程_设计原则和模式(CHM)"聚焦于Java语言在并发环境下的编程技巧、...

    Java并发编程:设计原则与模式(第二版)_阅读密码www.zasp.net_仅提供试看如需要请购买原版书

    Java提供了丰富的并发工具和API,包括线程、锁、同步、并发集合、并发工具类以及Java内存模型(JMM),这些都是Java并发编程的基础。 1. **线程与进程**:在Java中,线程是程序执行的基本单元,而进程是系统分配...

    JAVA并发编程艺术pdf版

    《JAVA并发编程艺术》是Java开发者深入理解和掌握并发编程的一本重要著作,它涵盖了Java并发领域的核心概念和技术。这本书详细阐述了如何在多线程环境下有效地编写高效、可靠的代码,对于提升Java程序员的技能水平...

    java并发编程与内存模型

    描述java并发编程原理 一.内存模型的相关概念 二.并发编程中的三个概念 三.Java内存模型 四..深入剖析volatile关键字 五.使用volatile关键字的场景

Global site tag (gtag.js) - Google Analytics