JAVA线程安全及性能的优化笔记(三)——Volatile关键字

前期回顾:

JAVA线程安全及性能的优化笔记(二)——Synchronized关键字

本期正文:

volatile是java提供的一种同步手段,只不过它是轻量级的同步,为什么这么说?因为volatile只能保证多线程的内存可见性,不能保证多线程的执行有序性。而最彻底的同步要保证有序性和可见性,例如synchronized。任何被volatile修饰的变量,都不拷贝副本到工作内存,任何修改都能及时写在主存。因此对于valatile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改时有序的。什么意思呢?

publicclassVolatileTest{
    publicvolatileinta;
    publicvoidadd(intcount){
        a=a+count;
    }
}

当一个VolatileTest对象被多个线程共享,a的值不一定是正确的,因为a=a+count包含了好几部操作,而此时多个线程的执行是无序的,因为没有任何机制来保证多个线程的执行有序性和原子性。volatile存在的意义是,任何线程对a的修改,都会马上被其他线程读取到,因为直接操作主存,没有线程对工作内存和主存的同步。所以,volatile的使用场景是有限的,在有限的一些情况下可以使用volatile变量代替锁。要使用volatile变量提供理想的线程安全,必须同时满足下面两个条件:

  1. 对变量的写操作不依赖于当前值
  2. 该变量没有包含在具有其他变量的不变式中

volatile只保证了可见性,所以volatile适合直接赋值的场景。

publicclassVolatileTest{
    publicvolatileinta;
    publicvoidseta(inta){
        this.a=a;
    }
}

在没有volatile声明时,多线程环境下,a的最终值不一定是正确的,因为this.a=a;涉及到给a赋值和将a同步回主存的步骤,这个顺序可能被打乱。如果用volatile声明了,读取主存副本到工作内存和同步a到主存的步骤,相当于是一个原子操作。所以简单来说,volatile适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。这是一种很简单的同步场景,这时候使用volatile的开销将会非常小。

站内很多人都问我,所谓线程的“工作内存”到底是个什么东西?有的人认为是线程的栈,其实这种理解是不正确的。看看JLS(java语言规范)对线程工作内存的描述,线程的workingmemory只是cpu的寄存器和告诉缓存的抽象描述。

可能很多人都觉得莫名其妙,说JVM的内存模型,怎么会扯到cpu上去呢?此时,我认为很有必要阐述下,免得很多人看的不明不白的。先抛开java虚拟机不谈,我们都知道,现在的计算机,cpu在计算的时候,并不总是从内存读取数据,它的数据读取顺序优先级是:寄存器—高速缓存—内存。线程消耗的是cpu,线程计算的时候,原始的数据来自内存,在计算过程中,有些数据可能被频繁读取,这些数据被存储在寄存器和高速缓存中,当线程计算完后,这些缓存的数据在适当的时候应该写会内存。当多个线程同时读写某个内存数据时,就会产生多线程并发问题,涉及到三个特征:原子性,有序性,可见性。在《线程安全总结》这篇文章中,为了理解方便,我把原子性和有序性统一叫做“多线程执行有序性”。支持多线程的平台都会面临这种问题,运行在多线程平台上支持多线程的语言应该提供解决该问题的方案。

那么我们看看JVM,JVM是一个虚拟的计算机,它也会面临多线程并发问题,java程序运行在虚拟机平台上,java程序员不可能直接去控制底层线程对寄存器高速缓存内存之间的同步,那么java从语法层面,应该给开发人员提供一种解决方案,这个方案就是诸如synchronized,volatile,锁机制(如同步块,就绪队列,阻塞队列)等等。这些方案只是语法层面的,但我们要从本质上去理解它,不能仅仅知道一个synchronized可以保证同步就完了。在这里我说的是jvm的内存模型,是动态的,面向多线程并发的,沿袭JSL的”workingmemory”的说法,只是不想闲扯到太多底层细节,因为《线程安全总结》这篇文章在说明怎样从语法层而去理解java线程同步,知道各个关键字的使用场景。

今天有人问我,那java的线程不是有栈吗?难道栈不是工作内存吗?工作内存这四个字得放到具体的场景中区描述,方能体现它具体的意义,在描述JVM的线程同步时,工作内存指的是寄存器和高速缓存的抽象描述,具体请自行参阅JLS。上面讲的都是动态的内存模型,甚至已经超出了JVM的范围,那么JVM的内存静态存储是怎样划分的?今天还有人问我,jvm的内存模型不是有eden区吗?也不见得你提起。我跟他说,这是两个角度去看的,甚至是不同的范围,动态的线程同步的内存模型,涵盖了cpu,寄存器,高速缓存,内存;jvm的静态内存存储模型只是一种对内存的物理划分而已,它只局限在内存,而且只局限在jvm的内存。那么线程栈,eden区都仅仅在jvm内存。

说说jvm的线程栈和有个朋友反复跟我纠结的eden区吧。jvm的内存,被划分了很多的区域

1. 程序计数器

每一个java线程都有一个线程计数器来用于保存程序执行到当前方法的哪一个指令。

2. 线程栈

线程的每个方法被执行的时候,都会同时创建一个帧(Frame)

用于存储本地变脸表、操作栈、动态链接、方法出入口等信息。每个方法的调用至完成,就意味着一个帧在VM栈中的入栈至出栈的过程。如果线程请求的栈深度大于虚拟机所允许的深度,讲抛出StackOverflowError异常;如果VM栈可以动态扩展(VMSpec中允许固定长度的VM栈),当扩展时无法申请到足够内存则抛出OutOfMemoryError异常。

3. 本地方法栈

4. 堆

每个线程的栈都是该线程私有的,堆则是所有线程共享的。当我们new一个对象时,该对象就被分配到了堆中。但是堆,并不是一个简单的概念,堆区又划分了很多区域,问为什么堆划分了这么多区域,这是为了JVM的内存垃圾收集,似乎越扯越远了,扯到垃圾收集了,现在的jvm的gc都是按代收集,堆区大致被分为三大块:新生代、旧生代、持久代(虚拟的);新生代又分为eden区,s0区,s1区。新建一个对象时,基本小的对象,生命周期短的对象都会放在新生代的eden区中,eden区满时,有一个小范围的gc(minorgc),整个新生代满时,会有一个大范围的gc(majorgc),讲新生代里的部分对象转到旧生代里。

5. 方法区

其实就是永久代(PermanentGeneration),方法区中存放了每个Class的结构信息,包括常量池、字段描述、方法描述等等。VMSpace描述中对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存,也可以选择固定大小或者可扩展外,甚至可以选择不现实垃圾收集。相对来说,垃圾收集行为在这个区域是相对比较少发生的,但并不是某些描述那样永久代不会发生GC(至少对当前主流的商业JVM实现来说是如此),这里的GC主要是对常量池的回收和对类的卸载,虽然回收的”成绩”一般也比较差强人意,尤其是类卸载,条件相当苛刻

6. 常量池

Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量表(constant_pooltable),用于存放编译期已可知的常量,这部分内容将在类加载后进入方法区(永久代)存放。但是Java语言并不要求常量一定只有编译期预置入Class的常量表的内容才能进入方法区常量池,运行期间也可将新内容放入常量池(最典型的String.intern()方法)

未完待续

推荐阅读:阿里8年Java架构师感悟——写给还在迷茫中的朋友

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

推荐阅读更多精彩内容