一、前言
最近在工作上用到了一個靜態方法,跟同事交流的時候,被一個問題給問倒了,只怪基礎不紮實...
問題大致是這樣的,“在多線程環境下,靜態方法中的局部變量會不會被其它線程給污染掉?”;
我當時的想法:方法中的局部變量在運行的時候,是存在JAVA棧中的,方法運行結束,局部變量也就都彈光了,理論上單線程的話是不會有問題的,我之所以不知道,是因爲不清楚在JAVA內存模型中,一個線程對應一個棧,還是多個線程共享一個棧...
其實如果知道每個線程都有一個自己的JAVA棧的話,問題也就很清楚了,不會被其它線程給污染掉;
當然,問題並不能止於此,這個問題已經暴露出自己對這方面比較薄弱,因此打算對JAVA內存模型和多線程併發問題做個小小總結;
二、JAVA中的內存模型
程序運行的時候,內存主要由以下部分組成:
- 堆:所有線程共享一個堆;存放的都是new 出來的對象;由垃圾回收器回收;
- 方法區:所有線程共享一個方法區;裏面存放的內容有點雜,可以認爲是除堆和棧中的其它東西(如類信息,靜態變量,常量,代碼等);Java虛擬機規範規定可以不對方法區進行垃圾回收,當並不是不回收,主要看具體虛擬機的實現,比如可以回收一些廢棄常量和無用的類;
- 程序計數器:也叫PC,存放下一條指令所在單元的地址的地方;
- JAVA棧:每個線程都有一個自己的JAVA棧;存放的一般是方法的局部變量,方法出口信息等;方法調用過程中,自動壓棧出棧;ps:棧空間大小是有限制的;
- 本地方法棧:與JAVA棧類似,區別是使用的對象不一樣,本地方法棧是給Native方法使用的,JAVA棧是給JAVA方式使用的;
三、多線程訪問共享內存情況
當多個線程執行同一個方法的時候,
什麼時候可能會出現異常結果:
多個線程共享一塊內存區域,在不加任何保護情況下,對其操作;
什麼時候可能會得到正確的結果:
不使用共享內存,每個線程內存空間相互獨立;
多線程共享一塊內存區域,但是對這塊共享區域加鎖訪問;
四、實例說明
情況一(多個線程共享一塊內存區域,在不加任何保護情況下,對其操作):
寫一個含靜態方法的類,求和,方法內用了一個靜態全局s(多個線程可以同時訪問):
package com.pichen.java.static_;
public class StaticTest {
private static int s = 0;
public static int sum(int n){
s = 0;
for(int i = 0; i <= n; i++){
s += i;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return s;
}
}
寫一個Thread,調用上面的靜態方法:
package com.pichen.java.static_;
public class ThreadCount implements Runnable{
@Override
public void run() {
while(true){
System.out.println(Thread.currentThread().getName() +":" +StaticTest.sum(100));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
寫個Main函數,起三個線程,觀察運行結果,基本都是錯誤的:
p
ackage com.pichen.java.static_;
public class Main {
public static void main(String[] args) {
ThreadCount t1 = new ThreadCount();
new Thread(t1).start();
ThreadCount t2 = new ThreadCount();
new Thread(t2).start();
ThreadCount t3 = new ThreadCount();
new Thread(t3).start();
}
}
運行結果不符合預期:
Thread-0:13968
Thread-1:13968
Thread-2:13968
Thread-0:13033
Thread-1:13033
Thread-2:13033
Thread-1:14725
Thread-0:14725
原因:多個線程同時對靜態全局變量s進行操作導致;
ps:這裏的例子是靜態全局變量s,其實有很多種情況會引起結果異常問題,如在main方法中new出了一個對象,new出來的對象是存放在堆中的,多個線程共享,此時如果多線程同時操作該對象的話,也是有可能產生錯誤結果;
情況二(不使用共享內存,每個線程內存空間相互獨立):
修改靜態sum方法,使用局部變量s,如下:
package com.pichen.java.static_;
public class StaticTest {
private static int s = 0;
public static int sum(int n){
int s = 0;
for(int i = 0; i <= n; i++){
s += i;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return s;
}
}
運行程序,結果正確:
Thread-1:5050
Thread-0:5050
Thread-2:5050
Thread-0:5050
Thread-2:5050
Thread-1:5050
Thread-0:5050
情況三(多線程共享一塊內存區域,但是對這塊共享區域加鎖訪問):
package com.pichen.java.static_;
public class StaticTest {
private static int s = 0;
public synchronized static int sum(int n){
s = 0;
for(int i = 0; i <= n; i++){
s += i;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return s;
}
}
運行程序,結果正確:
Thread-1:5050
Thread-0:5050
Thread-2:5050
Thread-0:5050
Thread-2:5050
Thread-1:5050
Thread-0:5050