iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >怎么理解Java并发可见性
  • 599
分享到

怎么理解Java并发可见性

2023-06-16 14:06:35 599人浏览 泡泡鱼
摘要

本篇内容介绍了“怎么理解java并发可见性”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!01 可见性的阐述可见性 的定义是:一个线程对共享变

本篇内容介绍了“怎么理解java并发可见性”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

01 可见性的阐述

可见性 的定义是:一个线程对共享变量的修改,另外一个线程能够立刻看到。

在单核时代,所有线程都在一个CPU上执行,所以一个线程的写,一定是对其它线程可见的。就好比,一个总经理下面就一个项目负责人。

怎么理解Java并发可见性

此时,项目经理查看到任务G后,分配给员工A和员工B,那么这个任务的进度就能随时掌握在项目经理手中了;每个员工都能从项目经理处得知最新的项目进度。

而在多核时代后,每个CPU都有自己的缓存,这就出现了可见性问题。

怎么理解Java并发可见性

此时,两个项目经理同时查看到任务G后,各自分配给自己下属员工,那么这个任务的进度就只能掌握在各自项目经理手中了,因为所有员工的工作进度并不是汇报给同一个项目经理;那么,每个员工只能得知自己项目组员工的工作进度,并不能得知其他项目组的工作进度。所以,当多个项目经理在做同一个任务时,就可能出现任务配比不均、任务进度拖延、任务重复进行等多种问题。

总和上面的例子来讲,就是因为进度的不及时更新,导致数据不是最新,导致决策失误。所以,我们隐约可以看出,内存并不直接与Cpu打交道,而是通过高速缓存与Cpu打交道。

cpu <&mdash;&mdash;> 高速缓存 <&mdash;&mdash;&mdash;> 内存

通过一张图片来表示就是(多核):

怎么理解Java并发可见性

下文我们的阐述,若无特殊说明,都是基于多核的。

02 原因分析

可见性问题都是由Cpu缓存不一致为并发编程带来,而其中的主要有下面三种情况:

2.1、线程交叉执行

线程交叉执行多数情况是由于线程切换导致的,例如下图中的线程A在执行过程中切换到线程B执行完成后,再切换回线程A执行剩下的操作;此时线程B对变量的修改不能对线程A立即可见,这就导致了计算结果和理想结果不一致的情况。

怎么理解Java并发可见性

2.2、重排序结合线程交叉执行

例如下面这段代码

int a = 0;    //行1    int b = 0;    //行2    a = b + 10;   //行3    b = a + 9;    //行4

如果行1和行2在编译的时候改变顺序,执行结果不会受到影响;

如果将行3和行4在变异的时候交换顺序,执行结果就会受到影响,因为b的值得不到预期的19;

怎么理解Java并发可见性

由图知:由于编译时改变了执行顺序,导致结果不一致;而两个线程的交叉执行又导致线程改变后的结果也不是预期值,简直雪上加霜!

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

因为主线程对共享变量的修改没有及时更新,子线程中不能立即得到最新值,导致程序不能按照预期结果执行。

例如下面这段代码:

package com.itquan.service.share.resources.controller;  import java.time.LocalDateTime;   public class VisibilityDemo {      // 状态标识flag     private static boolean flag = true;      public static void main(String[] args) throws InterruptedException {         System.out.println(LocalDateTime.now() + "主线程启动计数子线程");         new CountThread().start();          Thread.sleep(1000);         // 设置flag为false,使上面启动的子线程跳出while循环,结束运行         VisibilityDemo.flag = false;         System.out.println(LocalDateTime.now() + "主线程将状态标识flag被置为false了");     }      static class CountThread extends Thread {         @Override         public void run() {             System.out.println(LocalDateTime.now() + "计数子线程start计数");             int i = 0;             while (VisibilityDemo.flag) {                 i++;             }             System.out.println(LocalDateTime.now() + "计数子线程end计数,运行结束:i的值是" + i);         }     }  }

运行结果是:

怎么理解Java并发可见性

从控制台的打印结果可以看出,因为主线程对flag的修改,对计数子线程没有立即可见,所以导致了计数子线程久久不能跳出while循环,结束子线程。

对于这种情况,作为有强迫症的阿粉我当然不能忍,所以就引出了下一个问题:如何解决线程间不可见性

03 如何解决线程间不可见性

为了保证线程间可见性我们一般有3种选择:

3.1、volatile:只保证可见性

volatile关键字能保证可见性,但也只能保证可见性,在此处就能保证flag的修改能立即被计数子线程获取到。

此时纠正上面例子出现的问题,只需在定义全局变量的时候加上volatile关键字

// 状态标识flag

private static volatile boolean flag = true;

3.2、Atomic相关类:保证可见性和原子性

将标识状态flag在定义的时候使用Atomic相关类来进行定义的话,就能很好的保证flag属性的可见性以及原子性。

此时纠正上面例子出现的问题,只需在定义全局变量的时候将变量定义成Atomic相关类

// 状态标识flag

private static AtomicBoolean flag = new AtomicBoolean(true);

不过值得注意的一点是,此时原子类相关的方法设置新值和得到值的放的是有点变化,如下:

// 设置flag的值     VisibilityDemo.flag.set(false);          // 获取flag的值     VisibilityDemo.flag.get()

3.3、Lock: 保证可见性和原子性

此处我们使用的是Java常见的synchronized关键字。

此时纠正上面例子出现的问题,只需在为计数操作i++添加synchronized关键字修饰

synchronized (this) {         i++;     }

通过上面三种方式,阿粉我都得到类似如下的期望结果:

怎么理解Java并发可见性

然而,接下来阿粉我要对其中的volatile和synchronized关键字做一番较为详细的解释。

04  可见性-volatile

Java内存模型对volatile关键字定义了一些特殊的访问规则,当一个变量被volatile修饰后,它将具备两种特性,或者说volatile具有下列两层语义:

  • 第一、保证了不同线程对这个变量进行读取时的可见性。即一个线程修改了某个变量的值,  这个新值对其他线程来说是立即可见的。(volatile解决了线程间共享变量的可见性问题)。

  • 第二、禁止进行指令重排序, 阻止编译器对代码的优化

针对第一点,volatile保证了不同线程对这个变量进行读取时的可见性,具体表现为:

  • 使用 volatile 关键字会强制将在某个线程中修改的共享变量的值立即写入主内存。

  • 使用 volatile 关键字的话, 当线程 2 进行修改时, 会导致线程 1 的工作内存中变量的缓存行无效(反映到硬件层的话, 就是 CPU 的  L1或者 L2 缓存中对应的缓存行无效);

附一张CPU缓存模型图:

怎么理解Java并发可见性

  • 由于线程 1  的工作内存中变量的缓存行无效,所以线程1再次读取变量的值时会去主存读取。基于这一点,所以我们经常会看到文章中或者书本中会说volatile  能够保证可见性。

综上所述:就是用volatile修饰的变量,对这个变量的读写,不能使用 CPU 缓存,必须从内存中读取或者写入。

使用volatile无法保障线程安全,那么volatile的作用是什么呢?

其中之一:(对状态量进行标记,保证其它线程看到的状态量是最新值)

怎么理解Java并发可见性

volatile关键字是Java虚拟机提供的最轻量级的同步机制,很多人由于对它理解不够(其实这里你想理解透的话可以看看happens-before原则),而往往更愿意使用synchronized来做同步。所以接下来阿粉我再说说synchronized关键字。

05 可见性synchronized

怎么理解Java并发可见性

5.1、作用域

synchronized关键字的作用域有二种:

  • 1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法。

如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法。

这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法。

因为当修饰非静态方法的时候,定的是当前实例对象。

  • 2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized  static 方法。它可以对类的所有对象实例起作用。

因为当修饰静态方法的时候,锁定的是当前类的 Class 对象。

5.2、可用于方法中的某个区块中

除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

用法是:

synchronized(this){      }

它的作用域是当前对象;

5.3、不能继承

synchronized关键字是不能继承的,也就是说,基类的方法

synchronized f(){     // 具体操作 }

在继承类中并不自动是

synchronized f(){     // 具体操作 }

而是变成了

f(){     // 具体操作 }

继承类需要你显式的指定它的某个方法为synchronized方法;

综上3点所述:synchronized关键字主要有以下这3种用法:

  • 修饰实例方法:作用于当前实例加锁,进入同步代码前要获得当前实例的锁

  • 修饰静态方法:作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁

  • 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁

这三种用法就基本保证了共享变量在读取的时候,读取到的是最新的值。

5.4、JVM关于synchronized的两条规定

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

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

从上面的这两条规则也可以看出,这种方式保证了内存中的共享变量一定是最新值。

但我们在使用synchronized保证可见性的时候也要注意以下几点:

  • A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象;而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

  • B.每个对象只有一个锁(lock)与之相关联。Java 编译器会在 synchronized 修饰的方法或代码块前后自动加上加锁 lock() 和解锁  unlock(),这样做的好处就是加锁 lock() 和解锁 unlock() 一定是成对出现的,毕竟忘记解锁 unlock() 可是个致命的  Bug(意味着其他线程只能死等下去了)。

  • C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

“怎么理解Java并发可见性”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

--结束END--

本文标题: 怎么理解Java并发可见性

本文链接: https://www.lsjlt.com/news/284031.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 怎么理解Java并发可见性
    本篇内容介绍了“怎么理解Java并发可见性”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!01 可见性的阐述可见性 的定义是:一个线程对共享变...
    99+
    2023-06-16
  • java并发编程之原子性、可见性、有序性
    目录1原子性1.1java中的原子性操作2可见性2.1可见性问题2.2解决可见性问题3有序性3.1单个线程内程序的指令重排序3.2多线程内程序的指令重排序3.3保证有序性的解决方法3...
    99+
    2024-04-02
  • Java并发之原子性 有序性 可见性及Happen Before原则
    1.原子性(Atomicity) 原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。由Java内存模型来直接保证的原子性变量操作包括rea...
    99+
    2024-04-02
  • 如何理解java 并发中的原子性与可视性
    如何理解java 并发中的原子性与可视性?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。java 并发中的原子性与可视性实例详解并发其实是一种解耦合的策略,它帮助我们把做什么...
    99+
    2023-05-31
    java 并发 原子性
  • Java并发怎么理解
    本篇内容介绍了“Java并发怎么理解”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!什么是并发?用户通常认为计算机在同一时刻可以做多个事情是理...
    99+
    2023-06-02
  • 深入理解Java多线程与并发框(第③篇)——Java内存模型与原子性、可见性、有序性
    一、Java内存模型Java Memory Modle,简称 JMM,中文名称 Java内存模型,它是一个抽象的概念,用来描述或者规范访问内存变量的方式。因为各中计算机的操作系统和硬件不同,方式机制也可能不同,Java内存模型用于屏蔽(适配...
    99+
    2023-06-05
  • java并发编程关键字volatile保证可见性不保证原子性详解
    目录关于可见性关于指令重排volatile关键字可以说是Java虚拟机提供的最轻量级的同步机制,但对于为什么它只能保证可见性,不保证原子性,它又是如何禁用指令重排的,还有很多同学没彻...
    99+
    2024-04-02
  • 怎么理解Java诡异并发中的有序性
    这篇文章主要介绍“怎么理解Java诡异并发中的有序性”,在日常操作中,相信很多人在怎么理解Java诡异并发中的有序性问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么理解Ja...
    99+
    2024-04-02
  • Java的并发锁怎么理解
    本篇内容主要讲解“Java的并发锁怎么理解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java的并发锁怎么理解”吧!   Java 中的并发锁大致分为隐式锁...
    99+
    2024-04-02
  • java多线程Synchronized实现可见性原理解析
    Synchronized实现可见性原理 可见性 要实现共享变量的可见性,必须保证两点: 线程修改后的共享变量值能够及时从工作内存刷新到主内存中 其他线程能够及时把共...
    99+
    2024-04-02
  • java高并发高可用怎么实现
    要实现高并发和高可用的Java系统,可以采取以下几个步骤:1. 水平扩展:通过增加服务器数量来分担负载,可以使用负载均衡器来将请求分...
    99+
    2023-10-20
    java
  • java怎么处理高并发
    在处理高并发时,可以使用以下几种方法来提高Java应用程序的性能和并发处理能力:1. 使用线程池:通过使用线程池可以减少线程的创建和...
    99+
    2023-08-23
    java
  • java并发量大怎么解决
    当Java并发量大时,可以采取以下几种方法来解决:1. 线程池:使用线程池来管理并发线程的数量,通过控制线程池的大小,可以有效地控制...
    99+
    2023-08-19
    java
  • PHP并发处理:如何提高性能和可扩展性?
    随着互联网的发展,Web应用程序的复杂性和访问量不断增加,性能和可扩展性成为开发人员和架构师的关注焦点。PHP是一种流行的编程语言,但其默认情况下不支持多线程。因此,在高负载情况下,PHP应用程序的性能可能会受到影响。本文将介绍如何使用P...
    99+
    2023-07-01
    响应 索引 并发
  • java并发编程Lock锁可重入性与公平性分析
    目录一、相似之处:Lock锁 vs Synchronized 代码块二、Lock接口中的方法三、不同点:Lock锁 vs Synchronized 代码块四、锁的可重入性4.1. s...
    99+
    2024-04-02
  • Java并发之CAS原理详解
    目录开端1.代码1.1修改后的代码1.2代码改进:CAS模仿2.CAS分析2.1Java对CAS的支持2.2CAS实现原理是什么?2.3CAS存在的问题2.3.1什么是ABA问题?2...
    99+
    2024-04-02
  • Java多线程的原子性,可见性,有序性你都了解吗
    目录1.原子性问题2.可见性问题3.有序性问题总结问题: 1.什么是原子性、可见性、有序性? 1. 原子性问题 原子性、可见性、有序性是并发编程所面临的三大问题。 所谓原子操作,就是...
    99+
    2024-04-02
  • 详解java安全编码指南之可见性和原子性
    目录不可变对象的可见性保证共享变量的复合操作的原子性保证多个Atomic原子类操作的原子性保证方法调用链的原子性读写64bits的值不可变对象的可见性 不可变对象就是初始化之后不能够...
    99+
    2024-04-02
  • java高并发请求怎么解决
    在处理高并发请求时,可以采取以下几种方法来解决:1. 使用线程池:可以使用线程池来管理并发请求的处理。通过线程池,可以限制同时执行的线程数量,以避免系统资源被耗尽。2. 优化代码:对于需要执行时间较长的操作,可以对代码进行优化,减少执行...
    99+
    2023-08-11
    java
  • Kafka怎么处理数据的顺序性和并发性
    Kafka通过分区和副本的机制来处理数据的顺序性和并发性。 数据的顺序性:Kafka中的数据被分为多个分区,每个分区内的数据是有...
    99+
    2024-03-14
    Kafka
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作