静态方法类被多线程调用安全性

Posted yuanxulong198010

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了静态方法类被多线程调用安全性相关的知识,希望对你有一定的参考价值。

有如下测试类代码:

public class Test1 {

    static int FACTOR = 2;

    public static void main(String[] args) {
        int c = add(2, 3);
    }

    public static int add(int a, int b) {
        return a + b;
    }
}

问题:

    方法add在被多线程调用的时候有没有可能存在线程安全性的问题?

    例如,线程1调用add,传入参数3,5,期望得到的答案是8,而线程2调用时传入的参数是4,9,期望得到答案是13,在多线程调用的环境中,会不会存在计算出错的可能?

    答案:不可能。

原因:

    要理解这个问题,首先要简单了解一下jvm的内存模型,如下:

    

然后运行javap -c -v Test1,得到如下java字节码(只留最主要的)

public class com.chaoxing.Test1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#25         // java/lang/Object."<init>":()V
   #2 = Methodref          #3.#26         // com/chaoxing/Test1.add:(II)I
   #3 = Class              #27            // com/chaoxing/Test1
   #4 = Class              #28            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               LocalVariableTable
  #10 = Utf8               this
  #11 = Utf8               Lcom/chaoxing/Test1;
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               args
  #15 = Utf8               [Ljava/lang/String;
  #16 = Utf8               c
  #17 = Utf8               I
  #18 = Utf8               MethodParameters
  #19 = Utf8               add
  #20 = Utf8               (II)I
  #21 = Utf8               a
  #22 = Utf8               b
  #23 = Utf8               SourceFile
  #24 = Utf8               Test1.java
  #25 = NameAndType        #5:#6          // "<init>":()V
  #26 = NameAndType        #19:#20        // add:(II)I
  #27 = Utf8               com/chaoxing/Test1
  #28 = Utf8               java/lang/Object
{
  public com.chaoxing.Test1();
    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 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/chaoxing/Test1;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_2
         1: iconst_3
         2: invokestatic  #2                  // Method add:(II)I
         5: istore_1
         6: return
      LineNumberTable:
        line 8: 0
        line 9: 6
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  args   [Ljava/lang/String;
            6       1     1     c   I
    MethodParameters:
      Name                           Flags
      args

  public static int add(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: ireturn
      LineNumberTable:
        line 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0     a   I
            0       4     1     b   I
    MethodParameters:
      Name                           Flags
      a
      b
}

看一下调用过程,

    1.iconst_2 ,主函数main先把常量2压栈。

    2. iconst_3,把常量3压栈。

    3. invokestatic  #2,调用add方法,调用方法的时候,会创建一个栈帧(frame),栈帧组成结构如下图:

        

        然后从main方法中的操作数栈中弹出与被调用方法add的参数(此处是常量2,常量3),写入到add栈帧中的本地变量表中,并把add方法的栈帧标记为当前栈帧,此时线程的栈如下图(只是形象表示):

        

4. 此时开始调用add方法

    4.1:iload_0,从本地变量表中把第一个变量添加到操作数栈中(此处是常量2)。

    4.2:iload_1,从本地变量表中把第二个变量添加到操作数栈中(此处是常量3)。

    4.3:iadd,进行相加操作,该方法从操作数中弹出第一个变量和第二个变量,并进行相加操作,然后把结果写入到操作数栈中。

    4.4:ireturn:i代表整型值的返回,该方法从操作数栈中弹出一个数并写入到调用者的操作数栈中(此处是main方法的操作数栈)。

5. add方法结束,销毁add方法的栈帧,把当前栈帧指向main方法的栈帧,并且继续main方法的执行。

6. istore_1:从操作数中弹出一个整型值,此时是add方法返回的值5,并且把该值写入到本地变量表中。

7. return:程序结束。

    在程序执行的过程中,每一个线程都会有一个自己的堆栈(stack),在本例中,因为只有main线程调用了一个add方法,不考虑jvm内部其他方法调用,因此我们理解为只有一个堆栈,如果启动了多线程,那么每个线程都会有自己的堆栈,因此就算在多线程调用的过程中,因为堆栈是私有的,所以方法中的变量或者参数都是私有的,不存在互相冲突导致计算结果错误的问题,但是如果在方法中使用了可变的全局变量,则要注意存在线程安全问题。

以上是关于静态方法类被多线程调用安全性的主要内容,如果未能解决你的问题,请参考以下文章

静态方法类被多线程调用安全性

静态方法类被多线程调用安全性

设计模式Singleton

java多线程并发去调用一个类的静态方法安全性探讨

[转]java多线程并发去调用一个类的静态方法安全性探讨

多线程 - 多线程中使用静态方法存在线程安全的问题