前段时间在学习java的线程知识的时候,总觉得自己不能很清楚的明白一个概念,那就是什么是线程安全的?到底是什么造成了线程的不安全呢?
我百思不得其解,于是就各种的查找资料,发现造成线程的安全与否好像跟java的内存模型有一定的联系,于是就决定学习这一部分的内容,
下面就来一起认识一下java的内存模型,谈谈我对java内存的理解。
1.什么是java的内存模型?他要解决的问题是什么?
首先我们来看看下面这幅图(自己画的,希望各位大兄弟担待)
先来简单的说一下这个图的意思,比如多线程i++操作,我们第一步就是从主存里面取出i的值,然后在线程一里面进行+1操作,然后把i的值返回到主存里面,然后更新i的值,
但是在这个过程中,线程二也同时对i进行操作,它取到的值是多少?再加上线程三,或者更多呢?所以就会出现不是我们期待的结果
java的内存模型其实就是要解决线程安全问题,在多线程访问的情况下,程序运行的结果怎么才能是我们期望的。所以下面我们就要明白下面3个规则
(1)原子性
说明了该模型定义的规则针对原子级别的内容存在独立的影响,原理级别包括实例,静态变量,数组元素,只是在该规则中不包括方法中的局部变量。
简单而言就是每一个操作都必须是不可分割的。
(2)可见性
就是规定了什么情况下,一个线程可以访问另一个线程或者是影响另一个线程,比如从另一个线程的可见区域读取数据存入到另一个线程中去。
简单来说就是,一个线程的操作可以被其他线程所知道,这就是可见性。
(3)指令重排序
规则将会约束任何一个违背了规则调用的线程在操作过程中的一些顺序,排序问题主要围绕了读取,写入,赋值有关的序列。简单理解就是,java虚拟机可以根据机器的性能对我们所写的代码不是从上到下执行,前提是他们之间没有相互依赖,
比如:
int a = 1;
int b = 2;
正常来说是先进行a的操作,然后再到b的,但是把他们的执行顺序换过来会有区别吗?
2.然后重点说说可见性的问题,在java中保证可见性只需要用volatile关键字就行。
说到volatile,我们就不得不谈到synchronized关键字(想了解的可以看看我的另一篇博文点击打开链接),因为volatile是synchronized的轻量级实现,
但是volatile不能保证原子性,只能保证可见性,所以它不是线程安全的。这一点必须要明白和记住。
下面我们说说使用volatile关键字保证了什么,详细的学习还是得看书。
(1)使用了volatile关键字后,说明java虚拟机不能对被他修饰的变量进行重排序。
(2)volatile修饰的变量不会在线程的工作内存进行缓存,对他的使用要从主存中进行操作,保证可见性。
3.但是volatile不是能够解决所有的问题的,在多线程下,我们还要明白一个原则Happens-Before原则:
但是在介绍 happens-before 法则之前介绍一个概念:JMM 动作(Java Memeory ModelAction), Java 存储模型动作。
常见的动作有:变量的读写、监视器加锁和释放锁、线程的 start()和 join().
下面看看完整的Happens-Before原则:
(1)同一个线程中的每个动作都 happens-before 于出现在其后的任何一个动作。(原子性)
(2)对一个监视器的解锁 happens-before 于每一个后续对同一个监视器的加锁。(原子性)
(3)对 volatile 字段的写入操作 happens-before 于每一个后续的同一个字段的读操作。(原子性)
(4)Thread.start()的调用会 happens-before 于启动线程里面的动作。(先开始I线程)
(5)线程中的所有动作都 happens-before 于其他线程检查到此线程结束或者 Thread.join()中返回Thread.isAlive()==false。
(6)一个线程 A 调用另一个另一个线程 B 的 interrupt()都 happens-before 于线程 A 发现 B 被 A 中断(B 抛出异常或者 A 检测到 B 的 isInterrupted()或者 interrupted())。
(7)一个对象构造函数的结束 happens-before 与该对象的 finalizer 的开始(回收不能提前)
(8)如果 A 动作 happens-before 于 B 动作,而 B 动作 happens-before 与 C 动作,那么 A 动 作 happens-before 于 C 动作。(依赖的推论)
关于java的内存模型就谈到这,看了之后可能还会觉得很多的东西都没有理解,这是正常的,很多时候把一些东西记住了就好,用的多了自然会明白设计的初衷,后面会通过锁机制的学习,从而加深理解。