欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-17 18:51:47
...

背景

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

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

分析

前提

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

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

java中对共享变量的操作原理

i++的线程安全性问题分析
稍微做一下解释:共享变量存储在主内存中,当某个线程需要对共享变量进行操作时,需要将共享变量拷贝一份到自己的工作内存中(线程私有),操作完成之后,就会将最新的结果刷新到主存中。

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

解决方案

方案一(常见错误方案)

使用volatile字段对共享变量进行修饰。
volatile字段的作用是让改变量对其他所有线程可见,但是并不能保证操作的原子性。仍然会出现多个线程同时读取主内存变量的情况。
附一张volatile的原理图:
i++的线程安全性问题分析

方案二

加同步锁,比如使用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方法进行赋值,这里的操作并不能保证原子性,如果在多线程中,很可能会出现以下情况

Thread1 Thread2
r1=i r3=i
r2=r1+1 r4=r3+1
i=r2 i=r4

总结

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