第十六章:Java内存模型——Java并发编程实战

一、什么是内存模型,为什么要使用它

如果缺少同步,那么将会有许多因素使得线程无法立即甚至永远看到一个线程的操作结果

  • 编译器把变量保存在本地寄存器而不是内存中
  • 编译器中生成的指令顺序,可以与源代码中的顺序不同
  • 处理器采用乱序或并行的方式来执行指令
  • 保存在处理器本地缓存中的值,对于其他处理器是不可见

在单线程中,只要程序的最终结果与在严格串行环境中执行的结果相同,那么上述所有操作都是允许的

在多线程中,JVM通过同步操作来找出这些协调操作将在何时发生

JMM规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见

1、平台的内存模型

每个处理器都拥有自己的缓存,并且定期地与主内存进行协调,在不同的处理器架构中提供了不同级别的缓存一致性,即允许不同的处理器在任意时刻从同一个存储位置上看到不同的值。JVM通过在适当的位置上插入内存栅栏来屏蔽在JMM与底层平台内存模型之间的差异。Java程序不需要指定内存栅栏的位置,而只需通过正确地使用同步来找出何时将访问共享状态

2、重排序

  各种使操作延迟或者看似乱序执行的不同原因,都可以归为重排序,内存级的重排序会使程序的行为变得不可预测

1 Thread one = new Thread(new Runnable() {
2             public void run() {
3                 a = 1;
4                 x = b;
5             }
6         });

上述代码也会有以下结果:

3、Java内存模式简介

  Java内存模型是通过各种操作来定义的,包括变量的读/写操作,监视器的加锁和释放操作,以及线程的启动和合并操作

  JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Before,使在正确同步的程序中不存在数据竞争(缺乏Happens-Before关系,那么JVM可以对它们任意地重排序)

  • 程序顺序规则。如果程序中操作A在操作B之前,那么在线程中A操作将在B操作之前执行
  • 监视器锁规则。在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作之前执行。(显式锁和内置锁在加锁和解锁等操作上有着相同的内存语义) 
  • volatile变量规则。对volatile变量的写入操作必须在对该变量的读操作之前执行。(原子变量与volatile变量在读操作和写操作上有着相同的语义) 
  • 线程启动规则。在线程上对Thread.start的调用必须在该线程中执行任何操作之前执行
  • 线程结束规则。线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行,或者从Thread.join中成功返回,或者在调用Thread.isAlive时返回false
  • 中断规则。当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行(通过抛出InterruptException,或者调用isInterrupted和interrupted)
  • 终结器规则。对象的构造函数必须在启动该对象的终结器之前执行完成
  • 传递性。如果操作A在操作B之前执行,并且操作B在操作C之前执行,那么操作A必须在操作C之前执行。

4、借助同步

”借助(Piggyback)“现有同步机制的可见性属性,对某个未被锁保护的变量的访问操作进行排序(不希望给对象加锁,而又想维护它的顺序)

 

Happens-Before排序包括:

  • 将一个元素放入一个线程安全容器的操作将在另一个线程从该容器中获得这个元素的操作之前执行
  • 在CountDownLatch上的倒数操作将在线程从闭锁上的await方法返回之前执行
  • 释放Semaphore许可的操作将在从该Semaphore上获得一个许可之前执行
  • Future表示的任务的所有操作将在从Future.get中返回之前执行
  • 向Executor提交一个Runnable或Callable的操作将在任务开始执行之前执行
  • 一个线程到达CyclicBarrier或Exchange的操作将在其他到达该栅栏或交换点的线程被释放之前执行。如果CyclicBarrier使用一个栅栏操作,那么到达栅栏的操作将在栅栏操作之前执行,而栅栏操作又会在线程从栅栏中释放之前执行

 

二、发布

造成不正确发布的真正原因:"发布一个共享对象"与"另一个线程访问该对象"之间缺少一种Happens-Before的关系

1、不安全的发布

  除了不可变对象以外,使用被另一个线程初始化的对象通常都是不安全的,除非对象的发布操作是在使用该对象的线程开始使用之前执行

 

 1 public class UnsafeLazyInitialization {
 2     private static Object resource;
 3     
 4     public static Object getInstance(){
 5         if (resource == null){
 6             resource = new Object(); //不安全的发布
 7         }
 8         return resource;
 9     }
10 }

 

原因一:线程B看到了线程A发布了一半的对象

原因二:即使线程A初始化Resource实例之后再将resource设置为指向它,线程B仍可能看到对resource的写入操作将在对Resource各个域的写入操作之前发生。因为线程B看到的线程A中的操作顺序,可能与线程A执行这些操作时的顺序并不相同

2、安全发布

例:BlockingQueue的同步机制保证put在take后执行,A线程放入对象能保证B线程取出时是安全的

  借助于类库中现在的同步容器、使用锁保护共享变量、或都使用共享的volatile类型变量,都可以保证对该变量的读取和写入是按照happens-before排序的

  happens-before事实上可以比安全发布承诺更强的可见性与排序性

3、安全初始化模式

方式一:加锁保证可见性与排序性,存在性能问题

 1 public class UnsafeLazyInitialization {
 2     private static Object resource;
 3     
 4     public synchronized static Object getInstance(){
 5         if (resource == null){
 6             resource = new Object(); //不安全的发布
 7         }
 8         return resource;
 9     }
10 }

 

方式二:提前初始化,可能造成浪费资源

1 public class EagerInitialization {
2     private static Object resource = new Object();
3     public static Object getInstance(){
4         return resource;
5     }
6 }

方式三:延迟初始化,建议

1 public class ResourceFactory {
2     private static class ResourceHolder{
3         public static Object resource = new Object();
4     }
5     public static Object getInstance(){
6         return ResourceHolder.resource;
7     }
8 }

方式四:双重加锁机制,注意保证volatile类型,否则出现一致性问题

 1 public class DoubleCheckedLocking {
 2     private static volatile Object resource;
 3     public static Object getInstance(){
 4         if (resource == null){
 5             synchronized (DoubleCheckedLocking.class){
 6                 if (resource == null){
 7                     resource = new Object(); 
 8                 }
 9             }
10         }
11         return resource;
12     }
13 }

 

三、初始化过程中的安全性

  • 如果能确保初始化过程的安全性,被正确构造的不可变对象在没有同步的情况下也能安全地在多个线程之间共享
  • 如果不能确保初始化的安全性,一些本应为不可变对象的值将会发生改变

初始化安全性只能保证通过final域可达的值从构造过程完成时可见性。对于通过非final域可达的值,或者在构成过程完成后可能改变的值,必须采用同步来确保可见性

 

转载于:https://www.cnblogs.com/HectorHou/p/6054302.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/417812.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二进制包 mysql_二进制包安装MySQL数据库

1.1二进制包安装MySQL数据库1.1.1 安装前准备(规范)[rootMysql_server ~]# mkdir -p /home/zhurui/tools ##创建指定工具包存放路径[rootMysql_server ~]# wget http://ftp.ntu.edu.tw/pub/MySQL/Downloads/MySQL-5.5/mysql-5.5.32-linux2.6-x86_64.tar.gz ##下载二进制包[ro…

SELECT TOP n 性能测试

--快1. select top 100 * from test where c1 < 30000 order by c2 --慢2. select top 101 * from test where c1 < 30000 order by c2 1. is more than two times faster than 2. CREATE TABLE [dbo].[NUM]([n] int NOT NULL, s varchar(128) NULL, PRIMARY KEY CLUSTE…

【转】String.format详解

一、前言                            String.format 作为文本处理工具&#xff0c;为我们提供强大而丰富的字符串格式化功能&#xff0c;为了不止步于简单调用 String.format("Hello %s", "John");&#xff0c;下面将笔记整理…

vs2010帮助文件安装完全攻略

1.第一种情况&#xff0c;安装时自动安装了help viewer这个就比较方便了&#xff0c;只需要第一次选择一个合适的位置&#xff0c;将Iso里面的文件安装进来即可&#xff0c;几乎就是傻瓜式的下一步 2.安装s2010时没有安装&#xff0c;之后又使用过帮助文件&#xff0c;这个东西…

java node websocket_nodejs怎么实现webSocket接口即时通讯服务?

websocket工作方式:广播和收听 # 使用场景:金融/聊天室/……websocket:服务器支持广泛(node/java/php/c#……) 原生API操作复杂 可以使用第三方模块:socket.io 是在websocket的基础上创建的模块 优点:简单下载服务器端npm i socket.io客户端使用// 工作方式:事件绑定与事件触发…

java与sql2005连接数据库_Java链接数据库SQl Server2005

Java链接数据库SQL Server2005步骤&#xff1a;1.到微软官方网站下载2005的jdbc并解压&#xff0c;获得文件sqljdbc.jar。本人使用的是sqljdbc_1.2&#xff0c;解压后即为该文件。sql jdbc 1.2下载地址&#xff1a;2.将sqljdbc.jar拷贝到C:\Program Files\Java\jre1.5.0_08\lib…

Microsoft Team Foundation Server 2010安装指南

索引:介绍安装操作系统准备安装过程中的需要的用户账户安装IIS 7.0安装SQL Server 2008安装SQL Server 2008 SP1补丁安装TFS 2010配置TFS 2010&#xff08;以高级配置为例&#xff09;配置TFS Build服务安装序列号 正文&#xff1a; 介绍&#xff1a;微软官方发布了Microsoft T…

IE本地网页弹出黄条“限制网页脚本”

Internet选项 高级——点选“允许活动内容在我的计算机上的文件中运行”

java提高班_2020年最新天码营-Java提高班,资源教程下载

课程名称2020年最新天码营-Java提高班&#xff0c;资源教程下载课程介绍初学者先广在精&#xff0c;关注代码背后的实现&#xff0c;关注内功修炼&#xff0c;了解实现原理和思想&#xff0c;形成自己完整的技术体系&#xff0c;知识成片之后就容易触类旁通&#xff0c;进步的速…

Swift3.0语言教程替换子字符串

Swift3.0语言教程替换子字符串 Swift3.0语言教程替换子字符串,替换子字符串其实就是将字符串中的子字符串删除&#xff0c;然后再进行添加。为了让这一繁琐的过程变的简单&#xff0c;NSString提供了替换子字符串的3个方法&#xff0c;这3个方法分别为&#xff1a;replacingOcc…

反射的使用

//加载dll库Assembly dll Assembly.LoadFile("E:\whq\code\Test\DLLConsole\bin\Debug\Test.UI.dll");//获取所有类型&#xff08;类、接口……&#xff09;Type[] typeClass dll.GetTypes();foreach (Type t in typeClass){//获取所有公共成员FieldInfo[] fields …

7-10外连接查询

外连接&#xff1a; 至少返回一个表中的所有记录&#xff0c;根据匹配条件有选择性的返回另一张表的记录。 外连接特点&#xff1a; 1&#xff1a;参与外连接的表有主从之分。 2&#xff1a;以主表的每行数所匹配的从表的数据列将符合条件的数据值直接返回到结果集中。 3:对那些…

java调用dubbo服务器_dubbo源码分析-服务端注册流程-笔记

前面&#xff0c;我们已经知道&#xff0c;基于spring这个解析入口&#xff0c;到发布服务的过程&#xff0c;接着基于DubboProtocol去发布&#xff0c;最终调用Netty的api创建了一个NettyServer。那么继续沿着RegistryProtocol.export这个方法&#xff0c;来看看注册服务的代码…

React - S1

资料&#xff1a; 1. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript 进度&#xff1a; 教程 - 高级内容remaining; 参考remaining js 与 的区别[转] 1、对于string,number等基础类型&#xff0c;和是有区别的 1&#xff09;不同类型间比较&#xff0c;之比较“转…

java错位_java – 如何保护自己免受参数错位的影响

假设我有这个重要的方法&#xff1a;int generateId(int clientCode, int dataVersion) {return clientCode * 2 dataVersion % 2;}这两个参数都是int,因此使用错误的参数调用此方法非常容易,例如generateId(dataVersion,clientCode).它将被成功编译和执行.但生成的id将完全错…

(csc)Visual C# 2010 编译器选项.

Visual C# 2010 编译器选项 - 输出文件 -/out:<文件> 指定输出文件名(默认值: 包含主类的文件或第一个文件的基名称)/target:exe 生成控制台可执行文件(默认) (缩写: /t:exe)/target:winexe …

力扣Java解数独_LeetCode 力扣 37. 解数独

题目描述(困难难度)给定一个数独棋盘&#xff0c;输出它的一个解。解法一 回溯法从上到下&#xff0c;从左到右遍历每个空位置。在第一个位置&#xff0c;随便填一个可以填的数字&#xff0c;再在第二个位置填一个可以填的数字&#xff0c;一直执行下去直到最后一个位置。期间如…

T-SQL像数组一样处理字符串、分割字符串,遍历数组

T-SQL对字符串的处理能力比较弱&#xff0c;比如我要循环遍历象1,2,3,4,5这样的字符串&#xff0c;如果用数组的话&#xff0c;遍历很简单&#xff0c;但是T-SQL不支持数组&#xff0c;所以处理下来比较麻烦。下边的函数&#xff0c;实现了象数组一样去处理字符串。 一、按指定…

jquery 获取鼠标和元素的坐标点

获取当前鼠标相对img元素的坐标[javascript] view plaincopy $(img).mousemove(function(e) { varpositionXe.pageX-$(this).offset().left; //获取当前鼠标相对img的X坐标 varpositionYe.pageY-$(this).offset().top; //获取当前鼠标相对img的Y坐标 console…

java上转型对象特点_Java 浅析三大特性之一继承

上文Java 浅析三大特性之一封装我们说到Java是一个注重编写类&#xff0c;注重于代码和功能复用的语言。Java实现代码复用的方式有很多&#xff0c;这里介绍一个重要的复用方式——继承。在介绍继承之前&#xff0c;我们要明确一点&#xff0c;继承是一个比较复杂的编写类的方式…