关于Java中的可见性和有序性问题

 更新时间:2023年08月07日 08:54:48   作者:吃饭只吃猪脚饭  
这篇文章主要介绍了关于Java中的可见性和有序性问题,Java在诞生之初就支持多线程,自然也有针对这三者的技术方案,今天就学习一下Java如何解决其中的可见性和有序性导致的问题,需要的朋友可以参考下

Java 如何解决可见性和有序性问题

并发场景中,因可见性、原子性、有序性导致问题常常导致bug,Java在诞生之初就支持多线程,自然也有针对这三者的技术方案

今天就学习一下Java如何解决其中的可见性和有序性导致的问题,就引来了今天的主角儿——Java内存模型

什么是Java内存模型

​ 导致可见性的原因是缓存,导致有序性的原因是编译优化

解决可见性、有序性最直接的方法就是禁用缓存和编译优化,但这样会导致性能下降,我们应该想到的是如何按需禁用缓存和编译优化。

​ Java内存模型规范了JVM如何提供按需禁用缓存和编译优化的方法,这些方法包括volatilesynchronizedfinal三个关键字以及六项Happens-Before规则。

使用volatile困惑

volatile关键字在C语言也有,最原始的意义就是禁用CPU缓存。

例如:

volatile int x = 0

表达就是不能使用CPU缓存,必须从内存中读取或者写入。

看起来没啥问题,但是在实际使用却会带了困惑。

例如:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      System.out.println(x);
    }
  }
}

假如有A线程调用writer(),B线程调用reader(),输出来的x会是多少呢,在JDK1.5之前,x可能是0或者42,这是因为CPU缓存导致可见性问题,JDK1.5之后Java内存模型对volatile语义进行了增强,因此就引出了Happens-Before规则。

Happens-Before规则

Happens-Before规则就是保证前面的一个操作的结果对后续操作是可见的

它约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定遵守Happens-Before规则:

规则一:程序的顺序性规则

​ 这条规则是指在一个线程中,按照程序顺序,前面的操作 Happens-Before 于后续的任意操作。

这还是比较容易理解的,比如刚才那段示例代码,按照程序的顺序,第 6 行代码 “x = 42;” Happens-Before 于第 7 行代码 “v = true;”,这就是规则 1 的内容,也比较符合单线程里面的思维:程序前面对某个变量的修改一定是对后续操作可见的。

规则二:volatile 变量规则

​ 这条规则是指对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作,即对后面的读操作可见。

规则三:传递性

​ 这条规则是指如果A Happens-Before B,B Happens-Before C,那么 A Happens-Before C。

规则四:管程中锁的规则

​ 这条规则是指对一个锁的解锁Happens-Before于后续对这个锁的加锁。

管程是一种通用的同步原语,在Java中指的就是synchronized,synchronized是Java里对管程的实现。

tip: 在操作系统中,管程的定义如下: 管程是由一组数据以及定义在这组数据之上的对该组数据操作的操作组成的软件模块,称之为管程。

基本特性:

1. 局部于管程的数据只能被局部于管程内的过程所访问。

2. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据

3. 每次仅允许一个进程在管程中执行某个内部过程。

注意:由于管程是一个语言的成分,所以管程的互斥访问完全由编译程序在编译时自动添加,无需程序员关注

​ 管程中的锁在Java是隐式实现的,加锁以及释放锁都是编译器帮我们实现的。

synchronized (this) { //此处自动加锁
 // x是共享变量,初始值=10
 if (this.x < 12) {
   this.x = 12; 
 }  
} //此处自动解锁

规则五:线程start()规则

​ 这条是关于线程启动的,指主线程A启动子线程B后,子线程B能够看到主线程在启动子线程B前的操作。

规则六:join()规则

​ 这条是关于线程等待的,它是指主线程A等待子线程B完成(主线程A通过调用子线程B的join()方法实现),当子线程B完成后(主线程A中join()方法返回)

主线程能够看到子线程的操作。这里的“看到”,指的是对共享变量的操作。

通过调用子线程B的join()方法实现),当子线程B完成后(主线程A中join()方法返回)

主线程能够看到子线程的操作。这里的“看到”,指的是对共享变量的操作。

到此这篇关于关于Java中的可见性和有序性问题的文章就介绍到这了,更多相关Java可见性和有序性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于java使用钉钉机器人向钉钉群推送消息

    基于java使用钉钉机器人向钉钉群推送消息

    这篇文章主要介绍了基于java使用钉钉机器人向钉钉群推送消息,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Jaxb2实现JavaBean与xml互转的方法详解

    Jaxb2实现JavaBean与xml互转的方法详解

    这篇文章主要介绍了Jaxb2实现JavaBean与xml互转的方法,简单介绍了JAXB的概念、功能及实现JavaBean与xml互转的具体操作技巧,需要的朋友可以参考下
    2017-04-04
  • Java synchronized轻量级锁实现过程浅析

    Java synchronized轻量级锁实现过程浅析

    这篇文章主要介绍了Java synchronized轻量级锁实现过程,synchronized是Java里的一个关键字,起到的一个效果是"监视器锁",它的功能就是保证操作的原子性,同时禁止指令重排序和保证内存的可见性
    2023-02-02
  • Java 单向队列及环形队列的实现原理

    Java 单向队列及环形队列的实现原理

    本文主要介绍了Java 单向队列及环形队列的实现原理,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • kafka手动调整分区副本数的操作步骤

    kafka手动调整分区副本数的操作步骤

    这篇文章主要介绍了kafka手动调整分区副本数,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • java中HashSet的特点及实例用法

    java中HashSet的特点及实例用法

    在本篇文章里小编给大家整理的是一篇关于java中HashSet的特点及实例用法,有兴趣的朋友们可以学习下。
    2021-04-04
  • 在Eclipse中运行Solr 基础知识

    在Eclipse中运行Solr 基础知识

    Solr我还是个菜鸟,写这一些文章只是记录一下最近一段时间学习Solr的心得,望各位同仁不要见笑,还希望多多指点
    2012-11-11
  • Java提取2个集合中的相同和不同元素代码示例

    Java提取2个集合中的相同和不同元素代码示例

    这篇文章主要介绍了Java提取2个集合中的相同和不同元素代码示例,涉及对removeall方法的简单介绍,然后分享了主要的示例代码,具有一定借鉴价值,需要的朋友可以参考下。
    2017-11-11
  • MybatisPlus中的insert操作详解

    MybatisPlus中的insert操作详解

    这篇文章主要介绍了MybatisPlus中的insert操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Mybatis返回int或者Integer类型报错的解决办法

    Mybatis返回int或者Integer类型报错的解决办法

    这篇文章主要介绍了Mybatis返回int或者Integer类型报错的解决办法,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-12-12

最新评论