方法中定義的局部變量是否線程安全?
了解過JVM的都知道,堆和方法區(JDK1.8后叫元空間)是線程共享的
虛擬機棧、程序計數器(PC寄存器)和本地方法棧是線程私有的
虛擬機棧里面存的是一個一個的棧幀,這里的棧幀你也可以理解為此時正在運行的方法,如果此方法調用其他方法的話,在虛擬機棧中就形成了一個個棧幀堆疊的形態,相信讀到這里你已經對虛擬機棧的樣子有了感覺
接著,我們討論棧幀(方法
棧幀有五個部分組成
- 局部變量表
- 操作數棧
- 動態鏈接【方法的符號引用,在這里我們可以討論虛方法(在運行時確定方法,把符號引用轉為直接引用)和非虛方法(在編譯時確定方法,把符號引用轉為直接引用)】這里有很多可以討論的,靜態鏈接和動態鏈接,虛方法表等
- 方法返回地址(PC寄存器中的值)
- 其他
在這里講棧幀的組成只是為了給不熟悉的同學漲漲知識或者給大家復習一波。
接著,我們回到題目
你會發現方法區中定義的局部變量不就是存在于局部變量表中嘛,而局部變量表有存在于棧幀,棧幀存在于虛擬機棧,那它不就是線程私有的,也就是安全的嘛!
其實,這里面有些坑,看完下面這四個例子我相信你會恍然大悟!
//s1的聲明方式是線程安全的
public static void method1(){
//StringBuffer:線程不安全的
StringBuffer s1 = new StringBuffer();
s1.append("A");
s1.append("B");
...
}
解釋:
這個例子是線程安全的,s1為局部變量,返回值為void,只能被當前線程操作,是線程安全的。
注意:這里返回值為空(void)
注意:把StringBudiler作為參數傳進去
//先說結論,這個是線程不安全的
//StringBuilder是線程不安全的
public static void method2(StringBuiler sBuilder){
sBuilder.append("A");
sBuilder.append("B");
...
}
//我們在main操作一下
public static void main(String[] args){
StringBuilder s = new StringBuilder();
new Thread(()->{
s.append("a");
s.append("b");
}).start();
method2(s);
}
//它們會搶s的資源,是線程不安全的
//嚴格的來說sBuilder不是方法內的局部變量,它是形參的局部變量,形參也會存在局部變量表中
這次不傳參數,而是返回
//結論:不是線程安全的(有可能存在問題)
public static StringBuilder method3(){
StringBuilder s1 = new StringBuilder();
s1.append("A");
s1.append("B");
...
return s1;
}
//引用類型和基本類型不用多說了把
//基本類型包括:byte,short,int,long,char,float,double,Boolean,
//引用類型包括:類類型,接口類型和數組。
//因為StringBuilder是類,一返回出去可能被其他位置上的多個線程所調用
返回String,String有點特殊,因為它具有不變性,看源碼是String被final聲明
//結論:線程安全的
public static String method4(){
StringBuilder s1 = new StringBuilder();
s1.append("A");
s1.append("B");
...
return s1.toString();
}
2和3發生了逃逸,作用域不止在方法內部了
1和4未發生逃逸,是安全的
我們要明確一個概念:創建對象不一定在堆空間上創建,還可以在棧上創建
總結:method1和4是安全的,2和3是不安全,所以這道題方法中定義的局部變量不一定線程安全,要看具體使用。