并发编程-锁相关的内存语义

锁的内存语义本质上可以说是对共享变量的更新,能及时让其他线程观察到;并且通过内存屏障,组织编译器或处理器指令重排序,导致多线程下不一致的现象。

1. volatile内存语义

  见上一篇文章。

2. 锁的内存语义

(1)锁的释放和获取的内存语义

  当线程释放锁时,JMM会将本地内存中的共享变量同步到主内存中;

  当线程获取锁时,JMM会将该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。

(2)锁内存语义的实现

  ReentrantLock的实现依赖于AbstractQueuedSynchronizer(AQS),AQS使用一个整形的volatile变量(state)来维护同步状态。这个volatile变量是ReentrantLock实现的关键。

  编译器不会对volatile读与其后的任意内存操作重排序不会对volatile写与其前的任意操作重排序。CAS同时具有volatile读和写的内存语义,编译器不会对CAS前和后的任意内存操作重排序,其是通过底层处理器缓存锁定实现原子性的。

CAS同时具有volatile读和写的内存语义,故Java线程之间的通信存在4种方式:

  ① A线程写volatile变量,随后B线程读该变量;

  ② A线程写volatile变量,随后B线程使用CAS更新该变量;

  ③ A线程使用CAS更新volatile变量,随后B线程使用CAS更新该变量;

  ④ A线程使用CAS更新volatile变量,随后B线程读该变量。

concurrent包的实现,通用的实现模式:

  ① 声明共享变量为volatile

  ② 使用CAS的原子条件更新来实现线程之间的同步

  ③ 配合以volatile的读/写,及CAS所具有的volatile读和写的内存语义来实现线程间的通信。

AQS、非阻塞数据结构和原子变量类,都是使用该模式来实现的。(该图摘自java concurrent包的实现原理

3. final内存语义

(1)final重排序规则

  ① 在类实例化时,构造函数中对一个final域的写入,与后边对该类对象的引用之间不能重排序。防止引用到未初始化完全的对象

  首先对象是共享对象如通过static修饰的类实例。JMM禁止编译器将final域的写重排序到构造函数之外;编译器会在final域写入之后,构造函数返回之前插入StoreStore屏障。

  对于普通变量的赋值可能重排序到构造函数之外,此时另一个线程通过该对象访问普通变量时,可能还没有赋值。(具体实例参看《Java并发编程的艺术》)

  ②  初次读一个包含final域的对象,与初次读这个final域之间不能重排序。防止对象引用提前读,而对象还未初始化完全

  在读对象引用和对象final域时,JMM在两者之间插入LoadLoad屏障,禁止读final域重排序到对象引用前边,普通变量可能存在这种情况。

  ③ 对final域是数组或对象等引用类型的情况,有如下约束:在构造函数内对一个final引用的对象的成员域的写入,与随后对该类对象的引用之间不能重排序。

  即final引用的对象中所有成员都写完成后,才可以被其他线程引用。

(2)禁止在构造函数中将this复制给外边的对象引用,否则可能导致其他线程看到初始化不完全的实例。

原文地址:https://www.cnblogs.com/shuimuzhushui/p/11336130.html

时间: 2024-11-08 23:56:12

并发编程-锁相关的内存语义的相关文章

JAVA并发编程2_线程安全&内存模型

"你永远都不知道一个线程何时在运行!" 在上一篇博客JAVA并发编程1_多线程的实现方式中后面看到多线程中程序运行结果往往不确定,和我们预期结果不一致.这就是线程的不安全.线程的安全性是非常复杂的,没有任何同步的情况下,多线程的执行顺序是不可预测的.当多个线程访问同一个资源时就会出现线程安全问题.例如有一个银行账户,一个线程往里面打钱,一个线程取钱,要是得到不确定的结果那是多么可怕的事情. 引入: 例如下面的程序,在单线程下,执行两次i++理论上i的最终值是12,但是在多线程环境下则不

【并发编程】JMM:java内存模型抽象

本文试图向大家解释清楚JMM及其抽象模型,但不仅仅是一个介绍,更希望能讲清楚JMM内存模型抽象的原因. 一.JMM的概念: 二.JMM的抽象将内存内存模型分成线程私有的本地内存和所有线程共享的主存: 三.JMM抽象模型造成了并发编程中共享变量的内存可见性问题,为什么会造成?选择这样的抽象模型有什么好处?有什么样的方法来处理这个问题? 一.JMM JMM直译过来就是java内存模型(java memory model),他的更深层次的描述,"JMM是一个语言级的内存模型,通过屏蔽各个系统平台的差异

JAVA并发编程3_线程同步之synchronized关键字

在上一篇博客里讲解了JAVA的线程的内存模型,见:JAVA并发编程2_线程安全&内存模型,接着上一篇提到的问题解决多线程共享资源的情况下的线程安全问题. 不安全线程分析 public class Test implements Runnable { private int i = 0; private int getNext() { return i++; } @Override public void run() { // synchronized while (true) { synchro

【Java并发编程】并发编程大合集-值得收藏

http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅入深的学习顺序总结如下,点击相应的标题即可跳转到对应的文章    [Java并发编程]实现多线程的两种方法    [Java并发编程]线程的中断    [Java并发编程]正确挂起.恢复.终止线程    [Java并发编程]守护线程和线程阻塞    [Ja

【Java并发编程】并发编程大合集

转载自:http://blog.csdn.net/ns_code/article/details/17539599 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅入深的学习顺序总结如下,点击相应的标题即可跳转到对应的文章    [Java并发编程]实现多线程的两种方法    [Java并发编程]线程的中断    [Java并发编程]正确挂起.恢复.终止线程    [Java并发编程]守护线程和线程阻塞    [Java并发编程]Volatile关键字(上)

(转)《深入理解java虚拟机》学习笔记10——并发编程(二)

Java的并发编程是依赖虚拟机内存模型的三个特性实现的: (1).原子性(Atomicity): 原子性是指不可再分的最小操作指令,即单条机器指令,原子性操作任意时刻只能有一个线程,因此是线程安全的. Java内存模型中通过read.load.assign.use.store和write这6个操作保证变量的原子性操作. long和double这两个64位长度的数据类型java虚拟机并没有强制规定他们的read.load.store和write操作的原子性,即所谓的非原子性协定,但是目前的各种商业

【Java并发编程】6、volatile关键字解析&内存模型&并发编程中三概念

转自:http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来

《java并发编程的艺术》读书笔记-第三章Java内存模型(二)

一概述 本文属于<java并发编程的艺术>读书笔记系列,第三章java内存模型第二部分. 二final的内存语义 final在Java中是一个保留的关键字,可以声明成员变量.方法.类以及本地变量.可以参照之前整理的关键字final.这里作者主要介绍final域的内存语义. 对于final域,编译器和处理器要遵守两个重排序规则: 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序. 初次读一个包含final域的对象的引用,与随后初次读这

&lt;java并发编程的艺术&gt;读书笔记-第三章java内存模型(一)

一概述 本文属于<java并发编程的艺术>读书笔记系列,继续第三章java内存模型. 二重排序 2.1数据依赖性 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性.数据依赖分下列三种类型: 名称 代码示例 说明 写后读 a = 1;b = a; 写一个变量之后,再读这个位置. 写后写 a = 1;a = 2; 写一个变量之后,再写这个变量. 读后写 a = b;b = 1; 读一个变量之后,再写这个变量. 上面三种情况,只要重排序两个操作的执行顺序,