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

JAVA内存模型和线程安全

阅读更多

参考http://shift-alt-ctrl.iteye.com/blog/1845309

 

一.JAVA内存模型(JMM,JAVA Memory Model):

    运行时涉及到两种内存,主内存和工作区内存,其中工作区内存通常为CPU的高速缓存区用来加快内存数据读取操作的(各线程独立).所有的变量内容都存在主内存中,当需要对内存数据进行操作时,数据将会从主存中load到工作区缓存并由CPU计算和赋值操作,然后再由工作区内存write到主存中,读取时如果工作区内存中已经有(loaded)则直接使用;工作区内存保存了线程使用的变量的副本,线程不可以直接操作主内存,只能操作工作区内存,对于需要变更的变量,需要通过一系列回写指令集同步到主内存中.且工作区内存是线程独占的,主内存是线程共享的.如下为操作集:

  1. lock:对主内存中的变量"加锁",标记为一个线程持有.如果一个变量已经被lock,其他线程尝试lock将被阻塞,同一个线程可以多次lock,不过锁的引用次数将会+1,需要unlock同样次数,才能解锁.对一个变量lock将会导致清空工作区内存中此变量的副本,即当其他线程再次使用此变量时需要重新获取.
  2. unlock:对主内存中的变量"释放锁",释放锁的线程和持有锁的线程必须是同一个线程,无法对没有加锁的变量执行unlock.
  3. read:由工作区内存向主内存发出"read"操作,随后必须执行load操作.
  4. load:工作区内存中加载"read"操作指定的变量,并放入副本中.此指令需要和read保持顺序
  5. use:变量交付给执行引擎做计算,当JVM需要使用变量时,将会使用此操作.
  6. assign:在工作区内存中,将变量更新为某个值.一个被assign操作的变量,必须被write到主内存,如果没有被assign的变量不能被write到主内存.
  7. store:工作区内存向主内存发出"同步"操作.
  8. write:工作区内存将store操作指定的变量同步到主内存中.此操作需要和store保持顺序.

其中read->load,store->write指令必须按照顺序执行,即不能load一个没有被read操作指定的变量,也不能write一个没有被store操作指定的变量,不过这read + load/store + write不一定必须是连续的,其中间仍然可以有其他指令.(volatile有特例)

    volatile是java提供的轻量级变量同步机制,它确保了变量可见性,即任何一个线程修改了volatile修饰的变量,其他线程将立即可见.对于普通变量因为存在主内存和工作区内存的复制和同步,所以无法具备此特性.volatile变量存储在主内中,任何线程需要使用此变量时,必须再次read,因为其他线程对此变量的更改,将会被其他线程在使用此变量时立即获得新值.

    volatile只是保证了可见性,但是它并非线程安全,因为如果线程一旦read到此值然后进行计算,在尚未write到主内存时其他线程也做了同样的操作,那么volatile变量的最终结果将无法达到预期..如果期望volatile变量线程安全,必须同步或者CAS.volatile变量操作时,read->load->use三个操作是连续的,assign->store->write三个操作是连续的.

    通常volatile用在“值”类型上,引用类型也有用到,如FilterInputStream里的volatile InputStream in。volatile修饰的变量如果是对象或数组之类的,其含义是对象或数组的地址具有可见性,但是对象或数组内部的成员改变不具有可见性。

 

二.线程安全

    线程是执行任务的最小调度单元,内核线程是OS创建和管理的线程,它将有内核完成线程的切换以及调度(CPU调度).任何一个java线程都对应一个内核线程,即java线程的所有特性都基于内核并受制于内核.在linux和windows系统中,一个java线程就是底层的一个内核线程.java对线程的调度基于内核,在主流的系统中,广泛采用了"抢占式"调度机制,即线程都以"争抢CPU资源"的姿态来运行,最终被运行的线程将有内核的调度算法来决定,如果线程没有获得运行资源,那么线程将被"暂停".."协同式"调度已经不适合多线程(进程)的系统,它表现为线程之间互相"谦让",如果一个线程获得运行资源,那么它将一直运行下去直到结束,如果一个线程是"长时间"的,那么极有可能这个线程将独占一个CPU,而其他线程无法获得资源..

   线程状态:

  1. NEW:新创建线程,尚未开启.
  2. RUNNABLE:当前线程已经被启动或者正在被运行,处于此状态的线程标明即将或者已经得到了运行资源.
  3. WAITING:如果线程因为wait()/join()/LockSupport.park(this)/sleep()导致当前线程无法继续执行或者获得资源.
  4. BLOCKED:如果当前线程因为对象锁获取时,被"阻塞",那么线程将处于BLOCKED状态,此状态下,线程不会释放资源.
  5. TERMINATED:线程执行结束,资源即将被回收.

    在JAVA中(甚至任何语言或者平台中)确保线程安全的方式,无外乎"同步锁"和"CAS","同步锁"是一种粗暴而严格的同步手段,它强制对资源的访问必须队列化,一个资源在任何时候只能有一个线程可访问.在java中"synchronized"修饰词可以用来同步方法的调用,synchronized可以指定需要同步的对象,如果 没有指定,默认为当前对象,如果是static方法,则表示对Class同步.synchronized关键词在编译之后,最终会生成2个指令:monitorenter和monitorexit,执行引擎如果遇到monitorenter指令,将会尝试获取对象锁,如果获取成功,则锁计数器+1,同时工作区中的对象值将视为无效,重新从主存中load;monitorexit将导致锁计数器-1,即释放锁,此时将会把对象值从工作区缓存中write到主存中;如果计数器为0,则表示此对象没有被任何线程加锁.如果获取锁失败,当前线程阻塞.此外synchronized本身具有"重入性"语义,如果此对象上的monitor是当前线程,那么锁获取操作将直接成功.

   我们不再争论synchronized锁和ReentrantLock API锁谁更优秀,这一把双刃剑,性能方面两者在普通情况下(即无复杂递深的lock调用或者多层synchronized)性能几乎差不多,synchronized稍微优秀一些.但是ReentrantLock提供了多样化的控制以及Condition机制,可以帮助我们有效的控制并发环境中,让线程遵循条件的阻塞和唤醒;例如BlockingQueue的实现机制.

    CAS(Compare and swap),设计方式上更像一种"乐观锁",通过"比较"-"更新"这种无阻塞的手段实现数据在多线程下的"安全性".在JAVA中CAS操作遍布Atomic包下的API中,底层使用一个闭源的Unsafe.compareAndSwapInt(Object,valueOffset,expect,update),其中需要告知对象的内存地址.CAS会出现一个有趣的问题,就是ABA,即A变量被更改为B之后,再次被更改为A,此时对于持有A数据的线程尝试更改值是可以成功了,就像B值从来就没有出现过一样..其实吧,这个问题不是问题,既然有线程把数据更改为A,那么后续的线程操作就应该遵守现在的结果,而无需关注过去的过程.

 

三、补充

Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory,或叫工作区内存),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

多线程中的重要概念

1.1 可见性
也就说假设一个对象中有一个变量i,那么i是保存在main memory中的,当某一个线程要操作i的时候,首先需要从main memory中将i 加载到这个线程的working memory中,这个时候working memory中就有了一个i的拷贝,这个时候此线程对i的修改都在其working memory中,直到其将i从working memory写回到main memory中,新的i的值才能被其他线程所读取。从某个意义上说,可见性保证了各个线程的working memory的数据的一致性。
可见性遵循下面一些规则:

当一个线程第一次读取某个变量的时候,会从main memory中读取最新的;
当一个线程运行结束的时候,所有写的变量都会被flush回main memory中;
当一个线程释放锁后,所有的变量的变化都会flush到main memory中,然后一个使用了这个相同的同步锁的进程,将会重新加载所有的使用到的变量,这样就保证了可见性;
volatile的变量会被立刻写到main memory中,读则重新加载。
1.2 原子性
还拿上面的例子来说,原子性就是当某一个线程修改i的值的时候,从取出i到将新的i的值写给i之间不能有其他线程对i进行任何操作。也就是说保证某个线程对i的操作是原子性的,这样就可以避免数据脏读。
通过锁机制或者CAS(Compare And Set 需要硬件CPU的支持)操作可以保证操作的原子性。
1.3 有序性
假设在main memory中存在两个变量i和j,初始值都为0,在某个线程A的代码中依次对i和j进行自增操作(i,j的操作不相互依赖),
i++;
j++;
由于,所以i,j修改操作的顺序可能会被重新排序。那么修改后的ij写到main memory中的时候,顺序可能就不是按照i,j的顺序了,这就是所谓的reordering,在单线程的情况下,当线程A运行结束的后i,j的值都加1了,在线程自己看来就好像是线程按照代码的顺序进行了运行(这些操作都是基于as-if-serial语义的),即使在实际运行过程中,i,j的自增可能被重新排序了,当然计算机也不能帮你乱排序,存在上下逻辑关联的运行顺序肯定还是不会变的。但是在多线程环境下,问题就不一样了,比如另一个线程B的代码如下
if(j==1) {
    System.out.println(i);
}
按照我们的思维方式,当j为1的时候那么i肯定也是1,因为代码中i在j之前就自增了,但实际的情况有可能当j为1的时候i还是为0。这就是reordering产生的不好的后果,所以我们在某些时候为了避免这样的问题需要一些必要的策略,以保证多个线程一起工作的时候也存在一定的次序。JMM提供了happens-before 的排序策略。

 

通过对以上知识的理解,我们应该深刻认识到线程安全问题的敏感性了,无论是get还是set,都要严谨的考虑线程安全问题,并且在get的地方考虑能否用volatile代替同步锁

分享到:
评论

相关推荐

    java内存模型和线程安全

    java内存模型和线程安全,详细解析java的 堆栈模型和线程机制

    Java 内存模型

    总之,Java内存模型是Java编程中的一个基础性话题,它定义了Java程序在多线程环境下关于数据共享、线程通信和同步的规则和约束。深入掌握Java内存模型对于编写高效且正确的Java多线程程序有着不可忽视的作用。程序员...

    Java 高并发三:Java内存模型和线程安全详解

    Java内存模型(Java Memory Model,JMM)是Java多线程编程中一个至关重要的概念,它定义了共享变量在多线程环境下的行为规则,主要包括原子性、有序性和可见性三大特性。 1. 原子性 原子性是指一个操作是不可中断的...

    java内存模型和一些多线程的资料

    Java内存模型(JVM Memory Model,...以上就是关于Java内存模型和多线程的一些关键知识点,它们对于理解和编写高效、线程安全的Java程序至关重要。通过深入学习和实践,开发者可以更好地掌握Java平台的并发编程能力。

    cpu 内存模型和java内存模型

    Java程序员了解CPU以及相关的内存模型,对于深入理解...通过分析具体的编程问题,比如Java锁的不同实现方式、CPU缓存的工作机制等,可以帮助程序员更好地理解Java内存模型,在多线程环境下写出更加健壮和高效的代码。

    深入理解Java内存模型 pdf 超清版

    深入理解Java内存模型,不仅能够帮助我们编写出高效、线程安全的代码,还能在面临并发问题时提供有力的分析和解决手段。通过阅读《深入理解Java内存模型》这本书,开发者可以进一步掌握Java并发编程的核心技术,提升...

    深入理解Java内存模型

    Java内存模型是并发编程中一个至关重要的概念,它定义了共享变量的访问规则,以及这些变量如何在多线程环境下进行读写操作。在深入理解Java内存模型之前,我们需要先了解并发编程模型的分类,然后掌握Java内存模型的...

    java线程-Java内存模型

    Java线程-Java内存模型是Java并发编程中的关键概念,它描述了多个线程如何共享和访问内存资源,以及如何保证数据的一致性和安全性。Java内存模型(JMM)是Java虚拟机规范的一部分,用于定义程序中各个线程对共享变量...

    深度剖析java内存模型

    JVM对Java内存模型的实现将内存分为两部分:线程栈区和堆区。每个线程拥有自己的线程栈,其中包含了线程执行的方法调用信息和本地变量信息。线程只能访问自己的线程栈,这意味着线程中的本地变量对其他线程是不可见...

    JSR133中文版.pdf

    这些内容对于深入理解Java内存模型如何影响线程和对象的行为至关重要。 总的来说,该文档是Java内存模型和线程规范的一个详尽的解释,它定义了Java平台中多线程编程的规则和行为,包括锁的机制、happens-before原则...

    java内存模型(有助理解多线程)

    ### Java内存模型(有助理解多线程) #### JMM简介 Java内存模型(JMM,Java Memory Model)是Java虚拟机规范中一个重要的概念,它规定了程序中各种变量(包括实例字段、静态字段和数组元素)的访问规则,以及在...

    深入理解 Java 内存模型_程晓明_InfoQ_java_内存模型_

    Java内存模型,简称JMM(Java Memory Model),是Java编程语言规范的一部分,它定义了线程如何共享和访问内存,以及在多线程环境中如何保证数据一致性。理解JMM对于编写高效、正确且线程安全的Java代码至关重要。 ...

    java内存模型文档

    Java内存模型,简称JMM(Java Memory Model),是Java编程语言规范的一部分,它定义了线程如何共享和访问内存,以及在并发编程中如何处理数据一致性的问题。理解JMM对于编写高效、线程安全的Java代码至关重要。 1. ...

    Java内存模型的历史变迁

    为了确保线程间数据的一致性,Java内存模型规定了一系列复杂的规则来管理线程与共享变量之间的交互过程。 ##### 主内存与工作内存 - **主内存**:存储着各个线程共享的数据,其中每个共享变量都有一份master copy...

    深入理解java内存模型

    Java内存模型(Java Memory Model,JMM)是Java平台中非常关键的概念,它定义了线程如何共享和访问内存中的数据,以及在多线程环境下如何保证数据的一致性。这本书"深入理解Java内存模型"显然是为了帮助读者深入探讨...

    Java内存模型详解

    Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)的一部分,用于规定程序中的各种变量(包括实例字段、静态字段和数组元素等)在多个线程共享内存中的读写行为。JMM的主要目的是确保所有线程能够看到一致...

    java内存模型.pdf

    Java内存模型(JMM)是Java虚拟机...Java内存模型对于理解和编写高效、线程安全的代码至关重要。了解JMM的工作原理以及如何与其他编程概念(如同步和异步)交互,可以帮助开发者编写出更健壮、可移植的Java应用程序。

    深入理解 Java 内存模型

    Java 内存模型(Java Memory Model,简称 JMM)是 Java 平台中关于线程如何访问共享变量的一套规则,它定义了线程之间的内存可见性、数据一致性以及指令重排序等关键概念,对于多线程编程和并发性能优化至关重要。...

    java内存模型详解--非常经典

    总的来说,理解Java内存模型对于编写高效、线程安全的Java程序至关重要。通过合理地使用JMM提供的机制,开发者可以有效地解决并发编程中的各种挑战,提高软件的性能和可靠性。文档“Java内存模型.doc”可能包含更...

Global site tag (gtag.js) - Google Analytics