【转载】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对“值”类型的对象是有效的,对引用类型是没有意义的。

 

二.线程安全

    线程是执行任务的最小调度单元,内核线程是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,那么后续的线程操作就应该遵守现在的结果,而无需关注过去的过程.

时间: 2024-08-06 23:31:52

【转载】JAVA内存模型和线程安全的相关文章

java内存模型与线程(转) good

java内存模型与线程 参考 http://baike.baidu.com/view/8657411.htm  http://developer.51cto.com/art/201309/410971_all.htm http://www.cnblogs.com/skywang12345/p/3447546.html 计算机的CPU计算能力超强,其计算速度与 内存等存储 和通讯子系统的速度相比快了几个数量级, 数据加载到内存中后,cpu处理器运算处理时,大部分时间花在等待获取去获取磁盘IO.网络

JAVA内存模型和线程安全

一.JAVA内存模型(JMM,JAVA Memory Model):     运行时涉及到两种内存,主内存和工作区内存,其中工作区内存通常为CPU的高速缓存区用来加快内存数据读取操作的(各线程独立).所有的变量内容都存在主内存中,当需要对内存数据进行操作时,数据将会从主存中load到工作区缓存并由CPU计算和赋值操作,然后再由工作区内存write到主存中,读取时如果工作区内存中已经有(loaded)则直接使用;工作区内存保存了线程使用的变量的副本,线程不可以直接操作主内存,只能操作工作区内存,对

Java内存模型和线程安全的使用例子

网上很多资料在描述Java内存模型的时候,都会介绍有一个主存,然后每个工作线程有自己的工作内存.数据在主存中会有一份,在工作内存中也有一份.工作内存和主存之间会有各种原子操作去进行同步. 下图来源于 这篇Blog 但是由于Java版本的不断演变,内存模型也进行了改变.本文只讲述Java内存模型的一些特性,无论是新的内存模型还是旧的内存模型,在明白了这些特性以后,看起来也会更加清晰. 1. 原子性 原子性是指一个操作是不可中断的.即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干

深入理解Java内存模型系列篇

[本文转载于深入理解Java内存模型,可点击每个章节标题查看原文] 深入理解Java内存模型(一)--基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必

JVM学习(3)——总结Java内存模型

俗话说,自己写的代码,6个月后也是别人的代码--复习!复习!复习!涉及到的知识点总结如下: 为什么学习Java的内存模式 缓存一致性问题 什么是内存模型 JMM(Java Memory Model)简介 volatitle关键字 原子性 可见性 有序性 指令重排 先行发生--happen-before原则 解释执行和编译执行 其他语言(c和c++)也有内存模型么?   为什么需要关注Java内存模型?   之前有一个我实习的同事(已经工作的)反讽我:学(关注)这个有什么用? 我没有回答,我牢记一

深入理解Java内存模型(六) final

与前面介绍的锁和volatile相比较,对final域的读和写更像是普通的变量访问.对于final域,编译 器和处理器要遵守两个重排序规则: 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操 作之间不能重排序. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序. 下面,我们通过一些示例性的代码来分别说明这两个规则: public class FinalExample { int i; //普通变量 fin

深入理解Java内存模型(五) 锁

锁的释放-获取建立的happens before 关系 锁是java并发编程中最重要的同步机制.锁除了让 临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息. 下面是锁释放-获取 的示例代码: class MonitorExample { int a = 0; public synchronized void writer() { //1 a++; //2 } //3 public synchronized void reader() { //4 int i = a; //5 -

深入理解Java内存模型(四) volatile

volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解 volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这些单个 读/写操作做了同步.下面我们通过具体的示例来说明,请看下面的示例代码: class VolatileFeaturesExample { volatile long vl = 0L; //使用volatile声明64位的long型变量 public void set(long l) { vl

深入理解Java内存模型(三) 顺序一致性

数据竞争与顺序一致性保证 当程序未正确同步时,就会存在数据竞争.java内存模型规范对数 据竞争的定义如下: 在一个线程中写一个变量, 在另一个线程读同一个变量, 而且写和读没有通过同步来排序. 当代码中包含数据竞争时,程序的执行往往产生违反直觉的结果(前一章的示例正是如此).如果一 个多线程程序能正确同步,这个程序将是一个没有数据竞争的程序. JMM对正确同步的多线程程序 的内存一致性做了如下保证: 如果程序是正确同步的,程序的执行将具有顺序一致性(sequentially consisten