1、成員變量和靜態變量是否線程安全
-
如果它們沒有共享,則線程安全
-
如果它們被共享了,根據它們的狀態是否能夠改變,又分兩種情況
-
如果只有讀操作,則線程安全
-
如果有讀寫操作,則這段代碼是臨界區,需要考慮線程安全
-
2、局部變量是否線程安全
-
局部變量是線程安全的
-
但局部變量引用的對象則未必
-
如果該對象沒有逃離方法的作用訪問,它是線程安全的
-
如果該對象逃離方法的作用范圍,需要考慮線程安全
-
3、局部變量線程安全分析
public static void test1() {int i = 10;i++;
}
每個線程調用 test1() 方法時局部變量 i,會在每個線程的棧幀內存中被創建多份,因此不存在共享
public static void test1();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATICCode:stack=1, locals=1, args_size=00: bipush ? ? ? ?102: istore_03: iinc ? ? ? ? ?0, 16: returnLineNumberTable:line 10: 0line 11: 3line 12: 6LocalVariableTable:Start ?Length ?Slot ?Name ? Signature3 ? ? ? 4 ? ? 0 ? ? i ? I
如圖
局部變量的引用稍有不同,先看一個成員變量的例子
class ThreadUnsafe {ArrayList<String> list = new ArrayList<>();public void method1(int loopNumber) {for (int i = 0; i < loopNumber; i++) {// { 臨界區, 會產生競態條件method2();method3();// } 臨界區}}
?private void method2() {list.add("1"); ?// 訪問的同一個成員變量list}
?private void method3() {list.remove(0);// 訪問的同一個成員變量list}
}
執行
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {ThreadUnsafe test = new ThreadUnsafe();for (int i = 0; i < THREAD_NUMBER; i++) {new Thread(() -> {test.method1(LOOP_NUMBER);}, "Thread" + i).start();}
}
多運行幾次就會發現,其中一種情況是,如果線程2 還未 add,線程1 remove 就會報錯:
Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0at java.util.ArrayList.rangeCheck(ArrayList.java:657)at java.util.ArrayList.remove(ArrayList.java:496)at cn.itcast.n6.ThreadUnsafe.method3(TestThreadSafe.java:35)at cn.itcast.n6.ThreadUnsafe.method1(TestThreadSafe.java:26)at cn.itcast.n6.TestThreadSafe.lambda$main$0(TestThreadSafe.java:14)at java.lang.Thread.run(Thread.java:748)
分析:
-
無論哪個線程中的 method2 引用的都是同一個對象中的 list 成員變量
-
method3 與 method2 分析相同
將 list 修改為局部變量
class ThreadSafe {public final void method1(int loopNumber) {ArrayList<String> list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {method2(list);method3(list);}}
?private void method2(ArrayList<String> list) {list.add("1");}
?private void method3(ArrayList<String> list) {list.remove(0);}
}
那么就不會有上述問題了
分析:
-
list 是局部變量,每個線程調用時會創建其不同實例,沒有共享
-
而 method2 的參數是從 method1 中傳遞過來的,與 method1 中引用同一個對象
-
method3 的參數分析與 method2 相同
方法訪問修飾符帶來的思考?
如果把 method2 和 method3 的方法修改為 public 會不會帶來線程安全問題?
-
情況1:有其它線程調用 method2 和 method3
-
情況2:在 情況1 的基礎上,為 ThreadSafe 類添加子類,子類覆蓋 method2 或 method3 方法,即
class ThreadSafe {public final void method1(int loopNumber) {ArrayList<String> list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {method2(list);method3(list);}}
?private void method2(ArrayList<String> list) {list.add("1");}
?private void method3(ArrayList<String> list) {list.remove(0);}
}
?
class ThreadSafeSubClass extends ThreadSafe{@Overridepublic void method3(ArrayList<String> list) {new Thread(() -> {list.remove(0);}).start();}
}
這樣的話就會存在線程安全的問題。因為method3新開了一個線程,造成多個線程訪問同一個共享資源,就會存在線程安全的問題。
從這個例子就可以看出 private 或 final 提供【安全】的意義所在。