有资源网

搜索
有资源网 首页 编程语言 查看内容

java高并发系列 - 第4天:JMM相关的一些概念

2019-7-26 01:06| 发布者: admin| 查看: 166| 评论: 0

摘要: JMM(java内存模子),由于并发程序要比串行程序复杂很多,其中一个紧张原因是并发程序中数据访问划一性和安全性将会受到严峻寻衅。怎样保证一个线程可以看到精确的数据呢?这个题目看起来很白痴。对于串行程序来说,

JMM(java内存模子),由于并发程序要比串行程序复杂很多,其中一个紧张原因是并发程序中数据访问划一性安全性将会受到严峻寻衅。怎样保证一个线程可以看到精确的数据呢?这个题目看起来很白痴。对于串行程序来说,根本就是小菜一碟,假如你读取一个变量,这个变量的值是1,那么你读取到的一定是1,就是这么简朴的题目在并行程序中居然变得复杂起来。事实上,假如不加控制地任由线程胡乱并行,纵然本来是1的数值,你也大概读到2。因此我们需要在深入了解并行机制的前提下,再界说一种规则,保证多个线程间可以有小弟,精确地协同工作。而JMM也就是为此而生的。

JMM关键技能点都是围绕着多线程的原子性、可见性、有序性来创建的。我们需要先了解这些概念。

原子性

原子性是指操纵是不可分的,要么全部一起实行,要么不实行。在java中,其体现在对于共享变量的某些操纵,是不可分的,必须连续的完成。比如a++,对于共享变量a的操纵,实际上会实行3个步调:

1.读取变量a的值,假如a=1
2.a的值+1,为2
3.将2值赋值给变量a,此时a的值应该为2

这三个操纵中恣意一个操纵,a的值假如被其他线程窜改了,那么都会出现我们不渴望出现的效果。以是必须保证这3个操纵是原子性的,在操纵a++的过程中,其他线程不会改变a的值,假如在上面的过程中出现其他线程修改了a的值,在满足原子性的原则下,上面的操纵应该失败。

java中实现原子操纵的方法大致有2种:锁机制无锁CAS机制,后面的章节中会有先容。

可见性

可见性是指一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的。有些同砚会说修改同一个变量,那肯定是可以看到的,岂非线程眼盲了?

为什么会出现这种题目呢?

看一下java线程内存模子:

编程语言-java高并发系列 - 第4天:JMM相关的一些概念(1)

  • 我们界说的全部变量都储存在主内存
  • 每个线程都有本身独立的工作内存,内里保存该线程利用到的变量的副本(主内存中该变量的一份拷贝)
  • 线程对共享变量全部的操纵都必须在本身的工作内存中进行,不能直接从主内存中读写(不能越级)
  • 差别线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的通报需要通过主内存来进行。(同级不能相互访问)

线程需要修改一个共享变量X,需要先把X从主内存复制一份到线程的工作内存,在本身的工作内存中修改完毕之后,再从工作内存中回写到主内存。
假如线程对变量的操纵没有刷写回主内存的话,仅仅改变了本身的工作内存的变量的副本,那么对于其他线程来说是不可见的。
而假如另一个变量没有读取主内存中的新的值,而是利用旧的值的话,同样的也可以列为不可见。

共享变量可见性的实现原理:

线程A对共享变量的修改要被线程B及时看到的话,需要进过以下步调:

1.线程A在本身的工作内存中修改变量之后,需要将变量的值革新到主内存中
2.线程B要把主内存中变量的值更新到工作内存中

关于线程可见性的控制,可以利用volatilesynchronized来实现,后面章节会有详细先容。

有序性

有序性指的是程序按照代码的先后次序实行。

为了性能优化,编译器和处理器会进行指令冲排序,偶然候会改变程序语句的先后次序,比如程序。

int a = 1;  //1
int b = 20; //2
int c = a + b; //3

编译器优化后大概酿成

int b = 20;  //1
int a = 1; //2
int c = a + b; //3

上面这个例子中,编译器调解了语句的次序,但是不影响程序的终极效果。

在单例模式的实现上有一种双重查验锁定的方式,代码如下:

public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}

我们先看instance = new Singleton();

未被编译器优化的操纵:

  1. 指令1:分配一款内存M
  2. 指令2:在内存M上初始化Singleton对象
  3. 指令3:将M的地址赋值给instance变量

编译器优化后的操纵指令:

  1. 指令1:分配一块内存S
  2. 指令2:将M的地址赋值给instance变量
  3. 指令3:在内存M上初始化Singleton对象

现在有2个线程,刚好实行的代码被编译器优化过,过程如下:

编程语言-java高并发系列 - 第4天:JMM相关的一些概念(2)

终极线程B获取的instance是没有初始化的,此时去利用instance大概会产生一些意想不到的错误。

现在比力好的做法就是接纳静态内部内的方式实现:

public class SingletonDemo {
    private SingletonDemo() {
    }
    private static class SingletonDemoHandler{
        private static SingletonDemo instance = new SingletonDemo();
    }
    public static SingletonDemo getInstance() {
        return SingletonDemoHandler.instance;
    }
}

java高并发系列

  • java高并发系列 - 第1天:必须知道的几个概念
  • java高并发系列 - 第2天:并发级别
  • java高并发系列 - 第3天:有关并行的两个紧张定律
  • java高并发系列 - 第4天:JMM相关的一些概念
  • java高并发系列 - 第5天:深入理解进程和线程
  • java高并发系列 - 第6天:线程的基本操纵
  • java高并发系列 - 第7天:volatile与Java内存模子
  • java高并发系列 - 第8天:线程组
  • java高并发系列 - 第9天:用户线程和保卫线程
  • java高并发系列 - 第10天:线程安全和synchronized关键字
  • java高并发系列 - 第11天:线程克制的几种方式
  • java高并发系列 - 第12天JUC:ReentrantLock重入锁

java高并发系列连载中,总计估计会有四五十篇文章,可以关注公众号:javacode2018,获取最新文章。
编程语言-java高并发系列 - 第4天:JMM相关的一些概念(3)

java高并发系列交换群
编程语言-java高并发系列 - 第4天:JMM相关的一些概念(4)


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

路过

雷人

握手

鲜花

鸡蛋

最新评论

CF与DNF端游辅助
返回顶部