关于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可见性和有序性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • IDEA Debug启动tomcat报60659端口占用错误的解决

    IDEA Debug启动tomcat报60659端口占用错误的解决

    工作中将开发工具由Eclipse转为IntelliJ IDEA,在使用过程中遇到许多问题,其中60659端口占用错误对于不熟悉IDEA的开发者来说或许会比较头痛,本文就来解决一下这个问题
    2018-11-11
  • MyBatis属性名和字段名配置不一致的解决

    MyBatis属性名和字段名配置不一致的解决

    在使用MyBatis进行持久化框架开发时,经常会遇到属性名和数据库字段名不一致的情况,本文主要介绍了MyBatis属性名和字段名配置不一致的解决,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06
  • java绘制国际象棋与中国象棋棋盘

    java绘制国际象棋与中国象棋棋盘

    这篇文章主要为大家详细介绍了java绘制国际象棋与中国象棋棋盘,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-05-05
  • Spring Data Jpa 复杂查询方式总结(多表关联及自定义分页)

    Spring Data Jpa 复杂查询方式总结(多表关联及自定义分页)

    这篇文章主要介绍了Spring Data Jpa 复杂查询方式总结(多表关联及自定义分页),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Java用数组实现循环队列的示例

    Java用数组实现循环队列的示例

    下面小编就为大家带来一篇Java用数组实现循环队列的示例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • Java实战之鲜花商城系统的实现

    Java实战之鲜花商城系统的实现

    这篇文章主要介绍了如何利用Java语言实现鲜花商城系统,文中采用的技术有Spring、SpringMVC、Mybatis、JSP等,感兴趣的小伙伴可以了解一下
    2022-05-05
  • Mybatis注解开发@Select执行参数和执行sql语句的方式(最新详解)

    Mybatis注解开发@Select执行参数和执行sql语句的方式(最新详解)

    @Select 是 Mybatis 框架中的一个注解,用于执行 SQL 查询语句,并把查询结果映射到指定的 Java 对象中,这篇文章主要介绍了Mybatis注解开发@Select执行参数和执行sql语句的方式,需要的朋友可以参考下
    2023-07-07
  • Java解决LocalDateTime传输前端为时间的数组

    Java解决LocalDateTime传输前端为时间的数组

    本文主要介绍了Java解决LocalDateTime传输前端为时间的数组,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-03-03
  • java ThreadPool线程池的使用,线程池工具类用法说明

    java ThreadPool线程池的使用,线程池工具类用法说明

    这篇文章主要介绍了java ThreadPool线程池的使用,线程池工具类用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • Mybatis中resultMap的Colum和property属性详解

    Mybatis中resultMap的Colum和property属性详解

    这篇文章主要介绍了Mybatis中resultMap的Colum和property属性,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教。
    2022-01-01

最新评论