《成长之路 -JAVA并发系列》 - (二)并发理论JMM

  上一节我们知道了线程的基本概念,对多线程有了基本的认识。显然多线程远远不止这些,在并发编程时常常会出现线程安全的问题。那什么是线程安全呢?通俗讲在多线程下代码执行的结果与预期正确的结果不一致,该代码就是线程不安全的,否则则是线程安全的。

JMM

1.抽象结构:

JVM把JMM分为线程栈区和堆区。java线程 --> 工作内存 --> 主内存。线程运行时会将数据从主内存拷贝到工作内存,操作完成后再刷新到主内存。

2.共享数据:

共享数据就是线程需要拷贝到工作内存的数据,java程序中的实例域,静态域和数组元素都是共享数据。

3.线程栈区:

线程中局部变量如果是基本类型比如char,boolean,那么都会保存在线程栈区;但是如果局部变量是一个对象,那么对象会保存在堆中,线程栈区只保存对象引用。但是对象中方法的局部变量保存在线程栈区。

JMM带来的问题

1.可见性问题:

每个线程都有自己的工作内存,在将数据刷新到主内存前互相都是不可见了。这种情况我们可以使用volatile关键字或者加锁让线程修改过数据后立刻刷新到主内存保证可见性。

2.竞争问题:

两个线程改变同一个变量就会出现竞争问题。比如线程A从主内存取得变量n=1,线程B也取得变量n=1,这时两个线程都对变量进行+1操作,那么刷新到主内存时n=2。这种情况我们可以使用Synchronized关键字进行同步,也就是线程A在完成操作前线程B得不到n的值。

重排序

为了提高性能,编译器和处理器对指令重排序。

1.as-if-serial

重排序肯定不能改变程序的结果,这是前提,所以规定如果数据之间不存在依赖那么就允许它们进行重排序。但是对于我们程序猿来说,我们只关心程序结果,并不关心有没有进行重排序,所以我们把不改变程序结果的重排序叫作as-if-serial语义。不过as-if-serial只是对单线程下的程序有效

2.happen-before

在多线程下即使编译器遵循as-if-serial语义,但却不能保证线程是安全的。比如下面程序,线程A调用threadA方法,线程B调用threadB方法,每个线程a和flag不存在依赖,所以可以进行重排序,如果线程A发生重排序,线程B就会判断条件为true,读取变量a,但是线程A还没有给变量a赋值,就会出错。

int a = 0;
boolean flag = false;

public void threadA(){
    a = 1;
    flag = true;
}

public void threadB(){
    if (flag){
        int i = a+a;
    }
}
解决:

多线程下可以通过插入内存屏障来禁止重排序,具体的内存屏障这里不展开描述,我们只需要知道多线程下,编译器会自动插入内存屏障来保证程序的正确性。同样的,程序猿并不关心有没有重排序,我们把多线程下的内存可见性保证叫做happen-before规则。比如A happen-before B,那么A操作的结果对于B来说是可见的,且A操作一定在B操作之前完成。

总结:

1.java内存模型分为线程栈区和堆区。
2.线程运行时会将数据从主内存拷贝到工作内存。
3.拷贝就会造成一致性和竞争问题。
4.为了提高性能编译器和处理器会进行重排序。
5.单线程下数据没有依赖可以重排序,叫作as-if-serial语义。
6.多线程通过插入内存屏障禁止重排序保证程序正确性,叫作happen-before规则。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值