Thread线程安全

要理解线程安全我们首先举一个例子:
问题:

  • 启动两个线程,同时操作一个变量 v = 0

  • 一个线程对该变量执行 N 次 v++

  • 另一个线程对该变量执行 N 次 v–

  • 问,当两个线程都执行结束时,v 的值是多少?
    结论:
    理论上我们的期望值应该是0,但是实际上是一个随机值(也有可能出现0),并且当N越大时出现随机值的概率越高。
    通过这个例子我们就可以简单的给线程安全下一个定义,程序运行的结果假如我们100%符合我们的预期,不会出现有时正确有时错误,就是线程安全。
    再回到我们刚才那个例子,为什么会产生这样的结果?我们先要理解非常重要的几个概念。
    1.java中的一条语句,对应的不一定是一条字节码,更不一定是一条cpu指令。所以我们模拟刚才的v++过程产生的指令;实际上可能更加复杂。
    v++; 1.加载变量到cpu的寄存器上 。2.让cpu把寄存器上的值+1。3.把计算的v值再返回给内存上。
    2.线程调度具有随机性存在,什么时候cpu调度下来,什么时候cpu调度回去,我们并不知道。Add和Sub的流程是
    Add
    1.把v的值加载在register
    2.计算+1
    3.把结果保存回v
    Sub
    1.把v的值加载在register
    2.计算-1
    3.把结果保存回v

    在这里插入图片描述
    如果发生调度是这样的,那么本来是一次++,一次–,值本来是0,但是由于调度原因,v–的值被覆盖了。所以只能看到++,所以此时的v的值就是1。这就是产生随机值得原因。N越大,发生这种情况的机会越大,当然这调度的一种情况,还有多种情况,但是都道理相同。

那么什么情况下会出现线程不安全?
答:共享+修改。
也就是说出现线程不安全前提是线程之间是共享一份数据的,并且修改了这个共享数据。

紧接着我们需要了解java JVM虚拟机运行时内存区域中,那些位置的数据是共享的,那些是线程内部私有的。(图片转载)
在这里插入图片描述
局部变量——>栈帧@栈 不共享
对象的属性——>对象@堆 共享
类的静态属性——>类@方法区 共享
局部变量是私有的,不需要考虑线程安全问题。属性/对象,静态属性/类是共享的需要考虑安全问题。
考虑线程不安全时,有三点需要我们特点关心。1.原子性 2.内存可见性 3.代码重排序。
分别介绍:

  1. 原子性(atomic)——来源之前科学界认为原子不可再分
    java中 一组不能再被分割的操作,被称为保证原子性。
    关于一些赋值时的原子性。jvm设计时是按照32bit设置的,比如
    int i =1;
    short s=1;
    byte b=1; 都是具备原子性的
    long l=1; 是不具备原子性的。原因是这次赋值操作必须被分解为高32位赋值+低32位赋值。
    在这里插入图片描述

八种基本类型的bit位。

2.内存可见性
线程内存与主内存图示
在这里插入图片描述
在这里插入图片描述内存可见性问题:因为高速缓存具有隔离性(因为线程有自己的工作内存,线程要操作数据,先把数据从主内存加载到工作内存中,等到合适的机会再把在自己工作内存的结果同步到主内存)所以主内存的值不是最新的结果。
3.代码重排序
int a=1;
int b=1;
int c=1;
在这里插入图片描述在单线程情况下,要代码重排序必须要保证结果一致。
多线程情况下,代码重排序就可能导致线程安全问题。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值