热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java多线程编程内存可见性

什么是JAVA内存模型JavaMemoryModel(JAVA内存模型)描述线程之间如何通过内存(memory)来进行交互。具体说来,JVM中存在一个主

什么是JAVA 内存模型

        Java Memory Model (JAVA 内存模型)描述线程之间如何通过内存(memory)来进行交互。具体说来,JVM中存在一个主存区(Main Memory或Java HeapMemory),对于所有线程进行共享,而每个线程又有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。具体的如下图所示:

        JMM描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存中读取出变量这样的底层细节。

        所有的变量都存储在主内存中,每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中变量的一份拷贝)。

        JMM的有两条规定

        1、线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写;

        2、不同的线程之间无法直接访问其他线程工作内存中的变量,线程变量值的传递需要通过主内存来完成。

 

内存可见性

        可见性:是指一个线程对共享变量值的修改,能够及时地被其他线程看到。

        共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。

        共享变量可见性实现的原理:线程1对共享变量的修改要想被线程2及时看到,必须要经过如下2个步骤:

        1、把工作内存1中更新过的共享变量的值刷新到主内存中;

        2、把主内存中最新的共享变量的值更新到工作内存2中。

        指令重排序:代码书写的顺序与实际的执行顺序可能不同,指令重排序是编译器或处理器为了提高程序性能而做的优化。

        1、编译器优化的重排序(编译器优化);

        2、指令级并行重排序(处理器优化);

        3、内存系统的重排序(处理器优化)。

 

        而导致共享变量在线程间不可见的主要原因:

        1、线程的交叉执行;

        2、重排序结合线程的交叉执行;

        3、共享变量更新后的值没有在工作内存与主内存之间及时更新。

        要实现共享变量的可见性,必须保证两点:

        1、线程修改后的共享变量值能够及时从工作内存刷新到主内存中;

        2、其他线程能够及时把共享变量的最新值从主内存更新到自己的工作内存中。

 

Synchronized

        Synchronized的功能包括:原子性(同步) 与 可见性。

        JMM关于synchronized的两条规定:

        1、线程解锁前,必须把共享变量的最新值刷新到主内存中;

        2、线程加锁前,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁需要使用的是同一把锁)。

        线程解锁前对共享变量的修改在下次加锁时对其他线程可见。

 

线程执行互斥代码的过程

        1、获得互斥锁

        2、清空工作内存

        3、从主内存拷贝变量的最新副本到工作内存

        4、执行互斥代码

        5、将更改后的共享变量的值刷新到主内存

        6、释放互斥锁

synchronized实现可见性

        1、锁内部的代码互斥,能够保证线程不会交叉执行,重排序也一样,由于加锁,线程之间对共享变量的执行总是有序的。

        2、由JMM关于synchronized的两条规定保证,synchronized具有可见性功能。

程序实例:

public class SynchronizedTest {public static void main(String[] args) {final Service service = new Service();// 线程anew Thread(new Runnable() {@Overridepublic void run() {service.runMethod();}}).start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 线程bnew Thread(new Runnable() {@Overridepublic void run() {service.stopMethod();}}).start();System.out.println("已经发起了停止的命令了!");}
}class Service {// 共享变量private boolean isContinueRun = true;public void runMethod() {String anyString = new String();while(isContinueRun) {synchronized (anyString) {}}System.out.println("线程停止!!");}public void stopMethod() {isContinueRun = false;}
}


Volatile

        关键字volatile的主要作用就是使变量在多个线程间可见。深入来说:通过加入内存屏障和禁止重排序优化来实现的。

        1、对volatile变量执行写操作时,会在写操作后加入一条store屏障指令;

        2、对volatile变量执行读操作时,会在读操作后加入一条load屏障指令。

首先来看如下一个实例:

public class VolatileTest1 {public static void main(String[] args) {try {RunThread thread = new RunThread();thread.start();Thread.sleep(1000);thread.setRunning(false);System.out.println("已近给 isRunning 赋值为false了!!");} catch (InterruptedException e) {e.printStackTrace();}}}class RunThread extends Thread {private boolean isRunning = true;public boolean isRunning() {return isRunning;}public void setRunning(boolean isRunning) {this.isRunning = isRunning;}public void run() {System.out.println("进入run了!!");while(isRunning) {}System.out.println("线程被停止了!!");}
}

程序运行结果:


        代码:“System.out.println("线程被停止了!!");”一直无法得到执行。

        在启动RunThread.java线程时,变量privateboolean isRunning = true;存在于公共堆栈以及线程的私有堆栈中。JVM为了线程运行的效率,线程一直在私有堆栈中取得isRunning的值是true。而代码thread.setRunning(false);虽然被执行,更新的确实公共堆栈中的isRunning变量的值false,所以线程一直死循环状态。

        这个问题就是私有堆栈(线程工作内存)中的值和公共堆栈(主内存)中的值不同步造成的。解决这样的问题就要使用volatile关键字,它只要的作用就是当前线程访问isRunning这个变量时,强制性从公共堆栈中进行取值。

public class VolatileTest2 {public static void main(String[] args) {try {RunThread thread = new RunThread();thread.start();Thread.sleep(1000);thread.setRunning(false);System.out.println("已近给 isRunning 赋值为false了!!");} catch (InterruptedException e) {e.printStackTrace();}}static class RunThread extends Thread {volatile private boolean isRunning = true;public boolean isRunning() {return isRunning;}public void setRunning(boolean isRunning) {this.isRunning = isRunning;}public void run() {System.out.println("进入run了!!");while(isRunning) {}System.out.println("线程被停止了!!");}}
}





使用volatile关键字时的内存结构如图:




线程写volatile变量的过程:

        1、改变线程工作内存中volatile变量副本的值

        2、将改变后的副本的值从工作内存刷新到主内存

线程读volatile变量的过程:

        1、从主内存中读取volatile变量的最新值到线程的工作内存中

        2、从工作内存中读取volatile变量的副本


volatile不能保证volatile变量复合操作的原子性

例如number++操作分为:

        1、读取number的值

        2、将number的值加1

        3、写入最新的number的值

Volatile非原子性的特征

public class VolatileTest3 {public static void main(String[] args) {MyThread[] myThreadArray &#61; new MyThread[100];for (int i &#61; 0; i <100; i&#43;&#43;) {myThreadArray[i] &#61; new MyThread();}for (int i &#61; 0; i <100; i&#43;&#43;) {myThreadArray[i].start();}}static class MyThread extends Thread {volatile public static int count;private static void addCount() {for (int i &#61; 0; i <100; i&#43;&#43;) {count&#43;&#43;;}System.out.println("count &#61; " &#43; count);}public void run() {addCount();}}
}

无法保证最后的结果加到10000




保证number&#43;&#43;自增操作的原子性

1、使用synchronized关键字

2、使用ReentrantLock&#xff08;java.until.concurrent.locks包下&#xff09;

3、使用AutomicInterger&#xff08;java.util.concurrent.atomic包下&#xff09;

/**
*
* &#64;Description: 注意一定要添加static关键字&#xff0c;这样synchronized 与 static锁的内容就是MyThread.class类了&#xff0c;也就达到了同步的效果
*
*/
static class MyThread extends Thread {volatile public static int count;synchronized private static void addCount() {for (int i &#61; 0; i <100; i&#43;&#43;) {count&#43;&#43;;}System.out.println("count &#61; " &#43; count);}public void run() {addCount();}
}






volatile使用场合

要在多线程中安全的使用volatile变量&#xff0c;必须同时满足&#xff1a;

1、对变量的写入操作不依赖其当前值

        不满足&#xff1a;number&#43;&#43;&#xff0c;conut &#61; conut *5等

        满足&#xff1a;boolean变量&#xff0c;记录温度变化的变量等

2、该变量没有包含在具有其他变量的不等式中

        不满足&#xff1a;不等式 low


synchronized与volatile的比较

        1、volatile不需要加锁&#xff0c;比synchronized更轻量级&#xff0c;不会阻塞线程&#xff1b;并且volatile只能修饰变量&#xff0c;而synchronized可以修饰方法&#xff0c;以及代码块。

        2、从内存可见性角度看&#xff0c;volatile读相当于加锁&#xff0c;volatile写相当于解锁。

        3、synchronized既能保证可见性&#xff0c;又能保证原子性&#xff0c;而volatile只能保证可见性&#xff0c;无法保证原子性。

        4、关键字volatile解决的是变量在多个线程之间的可见性问题&#xff0c;而synchronized关键字解决的是多个线程之间访问资源的同步性。


推荐阅读
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 解决.net项目中未注册“microsoft.ACE.oledb.12.0”提供程序的方法
    在开发.net项目中,通过microsoft.ACE.oledb读取excel文件信息时,报错“未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序”。本文提供了解决这个问题的方法,包括错误描述和代码示例。通过注册提供程序和修改连接字符串,可以成功读取excel文件信息。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
author-avatar
低碳的S
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有