i++的线程安全性问题分析

背景

今天分享一道常见的面试题:i++是线程安全的吗?

既然这么问了,答案肯定是不安全啊,至于为啥不安全,咱们来说道说道

分析

前提
谈到线程安全问题,那什么情况下会出现线程安全的问题呢,就是当多个线程操作同一个共享变量的时候,就会出现线程安全问题;那共享变量又是指哪些呢,就是存储在堆中即主内存的变量信息,包括全局变量、对象实例、静态变量等。
而在方法内部的声明的临时变量是不会存在线程安全问题的,因为这些变量是存储在线程的工作内存中(即私有内存),线程与线程间是无法共享的。

**所以,我们讨论的线程安全问题一定是针对于全局变量而言,那就要区别面试题中i的范围,如果i是全局变量,则会出现安全问题;如果i是局部变量,则是线程安全的。**
java中对共享变量的操作原理

在这里插入图片描述
稍微做一下解释:共享变量存储在主内存中,当某个线程需要对共享变量进行操作时,需要将共享变量拷贝一份到自己的工作内存中(线程私有),操作完成之后,就会将最新的结果刷新到主存中。

这里的线程安全问题就在于,当一个线程将主存中的数据读取到自己的工作内存之后,没来操作完成,那另一个线程又将主存数据读取并操作,很显然,前者对于主存变量的操作就会被覆盖,从而引发线程安全问题。

解决方案

方案一(常见错误方案)

使用volatile字段对共享变量进行修饰。
volatile字段的作用是让改变量对其他所有线程可见,但是并不能保证操作的原子性。仍然会出现多个线程同时读取主内存变量的情况。
附一张volatile的原理图:
在这里插入图片描述

方案二

加同步锁,比如使用synchronized关键字修饰,保证只有一个线程可以对主存变量进行操作。

public class demo {
    private int value;

    public synchronized void increase() {
        value++;
    }
}
方案三

使用Atomic*类修饰来保证原子性

public class demo {
    private AtomicInteger value;

    public  void increase() {
        value.incrementAndGet();
    }
}

附:i++的字节码分析

以上说的都是从程序原理上说明的,咱们还可直接看一下底层的字节码是如何实现i++操作的。

java程序如下:

public class demo {
    private int value;

    public void increase() {
        value++;
    }
}

现将java代码进行编译,然后使用字节码查看命令,javap -v demo.class,如下:

{
  public com.imooc.miaoshaproject.demo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0

  public void increase();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field value:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field value:I
        10: return
      LineNumberTable:
        line 11: 0
        line 12: 10
}

咱们来主要看一下increase这个方法,很明显,在进行i++进行操作的时候,是现将i值取出,然后进行iadd操作,最后再调用putfield方法进行赋值,这里的操作并不能保证原子性,如果在多线程中,很可能会出现以下情况

Thread1Thread2
r1=ir3=i
r2=r1+1r4=r3+1
i=r2i=r4

总结

1、i++作用域在局部方法中是不会出现线程安全问题的,只有在全局变量中才会出现线程安全问题
2、volatile只能保证变量对其他线程的可见性,并不能保证原子性操作
3、可以对i++操作使用同步锁,或者使用Atomic*包修饰共享变量,来保证原子性操作

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值