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

背景

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

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

分析

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

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

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

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

解决方案

方案一(常见错误方案)

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

方案二

加同步锁,比如使用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."":()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*包修饰共享变量,来保证原子性操作

你可能感兴趣的:(java基础,i++的线程安全,volatile,java内存模型)