《深入理解Java虚拟机》(三)--Java内存模型与线程(1)

Java内存模型

Java的内存模型屏蔽掉了各种硬件和操作系统的内存访问差异,实现了Java跨平台的效果,C/C++语言使用的是物理硬件和操作系统的内存模型,所以不能实现跨平台。

1/1 主内存与工作内存

Java内存模型的主要目标是定义程序中各个变量的访问规则,这里说的变量包括实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,因为他们是线程私有的。
Java内存模型规定了所有的变量都存储在主内存上,每条线程有自己的工作内存,工作内存中保存了被该线程使用到的变量的内存副本,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写在主内存中的变量。不同的线程也无法访问对方的工作内存,线程之间变量值的传递均需要通过主内存来完成。线程、主内存、工作内存三者之间的交互关系如下:


线程、主内存、工作内存三者之间的交互关系

1/2 内存间交互操作

Java内存模型定义了以下8中操作来完成主内存与工作内存之间的数据交互。

  • lock(锁定):作用于主内存变量,将一个变量标记为一条线程独占的状态。
  • unlock(解锁):作用于主内存变量,它把一个处于锁定状态的变量释放出来,释放后变量才能被其他线程锁定。
  • read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作的使用。
    -load(载入):作用于工作内存中的变量,它把read操作的从主内存中得到的变量放入到工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎中得到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令的时候执行这个操作。
  • store(储存):作用于工作内存中,它把工作内存中的一个变量的值传送到主内存中,以便后面write的使用。
  • write(写入):作用于主内存中,它把stroe从工作内存中得到的变量的值放到指定的主内存的变量中。
    如果要把一个变量从主内存复制到工作内存,就要使用read load操作,如果要从工作内存同步回主内存就要使用store write操作,Java内存模型只要求这几个操作顺序执行,并没有要求连续执行(也就是他们之中可以插入别的操作),除此之外,Java内存模型还规定了上述8种操作必须满足如下规则:
  • 不允许read和load、store和write单独出现。
  • 不允许一个线程丢弃它最近的的assign操作,也就是说一个线程改变了它的变量之后必须同步到主内存中去。
  • 不允许一个线程没有发生过任何assign操作就把数据从工作内存同步到主内存中去。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化的变量。
  • 一个变量在同一时刻只允许一条线程对其lock,执行了多少次lock就要对应执行多少次unlock。
  • 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量之前,需要重新执行load或者assign操作。
  • 如果一个线程没有执行lock,就不许执行unlock,也不允许unlock一个被其他线程锁定住的变量。
  • 对一个变量执行unlock之前必须先把它同步到主内存之中。

1/3 volatile 型变量的特殊规则

被volatile修饰的变量具备两个特性,一个是保证此变量对所有线程的可见性,另一个是禁止指令重排序。

  • 可见性:
    对所有线程的可见性是指当一个线程修改了这个变量的值,这个值对于其他线程来说是立即可以见的。但这只是赋值操作,如果这个操作是一个运算(例如 a++;)。
    举个栗子:
public class Vola {
  public static volatitle int race = 0;
  public static void increase(){
     race++; 
  }
  private static final int T = 20;
  public static void main(String[] args){
  Thread[] threads = new Thread[T];
  for(int i=0; i<T; i++){
    threads[i] = new Thread(
      @Override
      public void run(){
        for(int i=0; i<10000; i++){
          increase();
        }
      }
    ); 
  threads[i].start();
  }
//等待所有累加线程都结束
while(Thread.activeCount()>1){
  Thread.yield();
}
System.out.println(race);
  }
}

这段代码发起了20个线程,每个线程对race变量进行1W次自增操作,如果能够正确并发的话,最后输出的结果应该是20W,但是结果却是一个小于20W的数。问题就是在于“race++”中,虽然在Java代码中它是一条命令,但是在Class文件中它是由四条字节码指令构成的

0:  getstatic #13;
3:  iconst_1
4:  iadd
5:  putstatic #13

当getstatic把race取出来时,volatile确实保证了race的值在此时的正确性,但是在执行iconst_1、iadd这些指令的时候,其他线程已经把race的值改变了。我的理解就是因为race++这句java代码不是原子性的。而race = 1或者race=null则可以保证原子性。

  • 禁止指令重排序
    什么是指令重排序?举个栗子
int a = 1;
int b = 2;

这两条java语句没有任何关联,java虚拟机在执行的过程中,为了优化代码执行的效率,可能会将两条语句(或者多条语句)在不改变其结果的情况下重新排序,然后执行。不光java虚拟机有指令重排序,CPU处理器也可能对输入的代码进行乱序执行优化。
举个栗子来看看指令重排序的影响:

Map config;
char[] configText;

//次变量必须为volatile变量
volatile boolean init = false;

//假设以下代码在线程A中执行
//模拟读取配置信息,当读取完成后将init的值设置为true来通知其他线程配置可用
config = new HashMap();
//读取配置文件
configText = readConfigFile(fileName);
//加载配置文件
proConfig(configText,config);
init = true;



//设置以下代码在线程B中执行
//等待init为true,代表线程A已经初始化配置文件完毕
while(!init){
    sleep();
}
//使用线程A中初始化的配置信息
dosomething(....);

如果init没有被volatile修饰,就可能由于指令重排序的优化,导致init = true;在还没有加载完成配置文件的时候被提前执行,这样B中使用配置信息的代码就可能出错自。volatile关键字则可以避免这种情况发生。
使用volatile修饰的变量会有一个内存屏障,指令重排序的时候不会把内存屏障后面的指令排到指令屏障的前面。
在大多数场景下,volatile的总开销要比锁(synchronize或者java.util.concurrent包中的锁)低。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容