转载请注明原创出处,谢谢!

首先需要说说线程安全?关于线程安全一直在提,比如StringBuilder和StringBuffer有什么区别? 经常就会出现关于线程安全与线程非安全,可能一直在提自己没有细细想想,如果忽然问你啥是线程安全的概念?可能你需要短暂停顿几秒,线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据,其实关于线程安全的定义我想不到好的,百度了下,也没有发现一个特别好的解释,我就选择一个相对来说还可以的解释吧 ,线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。我觉得该描述也不完全正确,因为现在控制并发的策略很多不仅仅是加锁机制,也可以不用加锁,我觉得这样可能比较合适的解释,就是多个线程都会操作到的,是一个公共资源或者共享的数据,但是每次操作只能一个线程使用而一旦临界区资源被占用其他的线程必须等待该资源的释放,在并行程序中,临界区资源都是受保护的那么就是线程安全,不包含的就是线程不安全的。

由于并发程序要比串行程序复杂很多,一个最重要的原因就是并发程序下访问的一致性和安全性将会受到严重挑战,如何保证一个线程可以看到正确的数据呢?因此我们需要深入了解并行机制的前提下,在定义一些规则来保证多线程直接有效的,正确的协同工作,而Java内存模型(JMM)就是来做这些事情的。

JMM模型都是围绕着多线程的原子性、可见性、和有序性来说的。这块内容过于复杂,自己水平有些,可能理解的有些偏差,希望到时候大家帮忙指出来。

原子性是指一个操作是不可中断的,即使在多线程一起执行的时候,一个操作如果开始就不会别其他线程干扰到。原文是:

Atomicity

  • Accesses and updates to the memory cells corresponding to fields of any type except long or double are guaranteed to be atomic. This includes fields serving as references to other objects. Additionally, atomicity extends to volatile long and double. (Even though non-volatile longs and doubles are not guaranteed atomic, they are of course allowed to be.)

  • Atomicity guarantees ensure that when a non-long/double field is used in an expression, you will obtain either its initial value or some value that was written by some thread, but not some jumble of bits resulting from two or more threads both trying to write values at the same time. However, as seen below, atomicity alone does not guarantee that you will get the value most recently written by any thread. For this reason, atomicity guarantees per se normally have little impact on concurrent program design.

long型字段和double型字段在32位hotspot可能不是原子性的,该如何证明呢?(作为一个思考题后续章节会进行解答,并且附上程序说明),在64位hotspot下面long型字段和double型字段都是原子性的。如果32位hotspot下volatile long 和volatile double也具有原子性 。为什么在32为hotspot加了volatile long型字段和double型字段字段就一定具有原型性了呢?,这与volatile的特性有关,当我们声明共享变量为volatile后,对这个变量的读、写会很特别,理解volatile的好方法就是把对volatile变量的单个读写堪称使用同一
锁对这些单个读写操作做了同步,锁的语义决定了临界区代码的执行具有原子性,所以在32为hotspot加了volatile long型字段和double型字段字段就一定具有原型性了,后续还会说volatile的,很难理解的关键词。

有序性,如果不是为了优化,为了性能,一般代码的执行顺序就是我们写的顺序从先到后一行一行执行,但是为了提高性能,我们需要优化,可能就会修改这些原先的顺序了,

目前的编译器和处理器常常会对指令做重排的。

  1. 编译器优化的重排序。编译器在不改变单线程语义的前提下,可以重新安排语句的执行顺序。
  2. 指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统的重排序。由于处理器换用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序中执行。上述的1属于编译器重排序,2和3属于处理器重排序。

这些重排序可能会导致多线程程序出现内存可见性问题。对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序(不是所有的编译器重排序都要禁止)。对于处理器重排序,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障指令,通过内存屏障指令来禁止特定类型的处理器重排序。关于内存屏障后续等深入了解了在来聊聊。

可见性是指当一个线程修改了某个共享变量的值,其他线程是否能够立即知道关于这个值的修改,可见性是一个复杂的综合性的问题,有一些关于缓存优化或者硬件优化会导致可见性的问题之外,上面提到的关于指令重排也会影响到可见性问题。

还有几个概念介绍:

happens-before简介

从JDK 5开始,Java使用新的JSR-133内存模型(除非特别说明,本文针对的都是JSR-133内存模型)。JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。
与程序员密切相关的happens-before规则如下:

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
  • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
  • 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。

    注意:两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。happens-before的定义很微妙,一个happens-before规则对应于一个或多个编译器和处理器重排序规则。对于Java程序员来说,happens-before规则简单易懂,它避免Java程序员为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现方法。

数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分为下列3中类型:

  1. 写后读,a=1;b=a;写一个变量之后,再读这个变量。
  2. 写后写,a=1;a=2;写一个变量之后,再写这个变量。
  3. 读后写,a=b;b=1;读一个变量之后,再写这个变量。

上面3种情况,只要重排序两个操作的执行顺序,程序的执行结果就会被改变。前面提到过,编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。
这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

as-if-serial语义

as-if-serial语义的意思是:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。
as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器、runtime和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

关于final和volatile后续章节介绍,很复杂很综合性的知识,今天这些内容很难理解,希望对大家有所帮助。


个人公众号

系统学习java高并发系列三的更多相关文章

  1. 系统学习java高并发系列一

    转载请注明原创出处,谢谢! JAVA服务端或者后端需要大量的高并发计算,所以高并发在JAVA服务端或者后端编程中显的格外重要了. 首先需要有几个概念: 1.同步和异步 同步异步是来形容方法的一次调用的 ...

  2. 系统学习java高并发系列二

    转载请注明原创出处,谢谢! 什么是线程? 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程 ...

  3. java高并发系列 - 第22天:java中底层工具类Unsafe,高手必须要了解

    这是java高并发系列第22篇文章,文章基于jdk1.8环境. 本文主要内容 基本介绍. 通过反射获取Unsafe实例 Unsafe中的CAS操作 Unsafe中原子操作相关方法介绍 Unsafe中线 ...

  4. java高并发系列-第1天:必须知道的几个概念

    java高并发系列-第1天:必须知道的几个概念 同步(Synchronous)和异步(Asynchronous) 同步和异步通常来形容一次方法调用,同步方法调用一旦开始,调用者必须等到方法调用返回后, ...

  5. java高并发系列 - 第21天:java中的CAS操作,java并发的基石

    这是java高并发系列第21篇文章. 本文主要内容 从网站计数器实现中一步步引出CAS操作 介绍java中的CAS及CAS可能存在的问题 悲观锁和乐观锁的一些介绍及数据库乐观锁的一个常见示例 使用ja ...

  6. java高并发系列 - 第25天:掌握JUC中的阻塞队列

    这是java高并发系列第25篇文章. 环境:jdk1.8. 本文内容 掌握Queue.BlockingQueue接口中常用的方法 介绍6中阻塞队列,及相关场景示例 重点掌握4种常用的阻塞队列 Queu ...

  7. java高并发系列 - 第5天:深入理解进程和线程

    进程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.程序是指令.数据及其组织形式的描述,进程是程序的实体. 进程具有的 ...

  8. Java高并发系列——检视阅读

    Java高并发系列--检视阅读 参考 java高并发系列 liaoxuefeng Java教程 CompletableFuture AQS原理没讲,需要找资料补充. JUC中常见的集合原来没讲,比如C ...

  9. java高并发系列 - 第6天:线程的基本操作

    新建线程 新建线程很简单.只需要使用new关键字创建一个线程对象,然后调用它的start()启动线程即可. Thread thread1 = new Thread1(); t1.start(); 那么 ...

随机推荐

  1. jsp基本语法及运行原理

    一.jsp简介 JSP全名为Java Server Pages,中文名叫java服务器页面,其根本是一个简化的Servlet设计,它 是由Sun Microsystems公司倡导.许多公司参与一起建立 ...

  2. 这可能是新手最容易入门的JVM讲解(不过是一场恋爱)

    作者:请叫我红领巾,转载请注明出处http://www.cnblogs.com/xxzhuang/p/7453746.html 一.写在前面 首先,本篇文章并没有涉及原理,而是在笔者撸了<深入理 ...

  3. Nuget安装nupkg文件

    问题描述: VS出现故障不能使用Manage NuGet Package自动下载安装DLL 解决方案: 直接官网下载相关需要的DLL 在VS中使用console加载本地下载的包安装 控制台code参考 ...

  4. web本地存储

    Web本地存储 通过本地存储(Local Storage),web 应用程序能够在用户浏览器中对数据进行本地的存储. 在 HTML5 之前,应用程序数据只能存储在 cookie 中,包括每个服务器请求 ...

  5. 关于回文串的DP问题

    问题1:插入/删除字符使得原字符串变成一个回文串且代价最小 poj 3280 Cheapest Palindrome 题意:给出一个由m中字母组成的长度为n的串,给出m种字母添加和删除花费的代价,求让 ...

  6. Java的HashMap实现原理整理总结

    通过Debug 探寻Java-HashMap 实现原理: 一个简单的例子,代码如下, 测试方法 main: public static void main(String[] args) { KeyOb ...

  7. MEF IOC使用

    IOC介绍 IOC:控制反转,DI:依赖注入.按我的理解应该是一个东西.作用目前我看到的主要是解除各个层之间的强耦合,实现接口分离.MEF优点: 1.net4 自带,无需安装扩展(引用System.C ...

  8. Docker 集群环境实现的新方式

    近几年来,Docker 作为一个开源的应用容器引擎,深受广大开发者的欢迎.随着 Docker 生态圈的不断建设,应用领域越来越广.云计算,大数据,移动技术的快速发展,加之企业业务需求的不断变化,紧随技 ...

  9. js变量以及其作用域详解

    详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp73   一.变量的类型  Javascript和Java.C这些语言不同 ...

  10. Servlet和JSP生命周期概述

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt374 Servlet生命周期分为三个阶段: 1,初始化阶段  调用init( ...