腦海第一感覺 static int 聲明的屬性一定是非線程安全的。int直接聲明的屬性難道也是非線程安全嗎?(疑問)。
通過題面意思就能感覺到面試官的意圖,他就是想讓你說是非線程安全的。然後他好問爲什麼。結果我直接說不知道。說實話真拿不準,於是自己通過實踐驗證得出了一些結論並記錄下來。加申印象。
private static int value = 1;
private int value = 1;
以下想通過實踐證明幾點:
1.兩種聲明方式是否線程安全。
2.總結兩種方式的區別。
第一兩種聲明方式是否線程安全。
證明1:private static int value = 1; 非線程安全
/**
* 證明static int 聲明屬性爲非線程安全的類
*/
class TTT {
static int value = 1; //註釋1:Integer value = new Integer(1) 同樣
public int get1() throws InterruptedException {
Thread.sleep(10); //註釋2:值越大重複值越多
return value++;
}
}
/**
* 測試類
*/
public class Test2 {
public static void main(String[] args) {
TTT t = new TTT(); //註釋3:實例化一個對象,並通過多個線程調用 get1 方法。
for(int i=1; i<=1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(t.get1());
} catch (Exception e) {
}
}
}).start();
}
}
}
期望結果:1 - 1000
實際結果:1 - x (<1000) 實際輸出結果中存在重複值
將已上代碼片段稍作調整再次驗證。
註釋3處,實例化對象挪到線程run方法體內
/**
* 證明static int 聲明屬性爲非線程安全的類
*/
class TTT {
static int value = 1; //註釋1:Integer value = new Integer(1) 同樣
public int get1() throws InterruptedException {
Thread.sleep(10); //註釋2:值越大重複值越多
return value++;
}
}
/**
* 測試類
*/
public class Test2 {
public static void main(String[] args) {
for(int i=1; i<=1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
TTT t = new TTT(); //註釋3:實例化1000個對象,並調用 get1 方法。
try {
System.out.println(t.get1());
} catch (Exception e) {
}
}
}).start();
}
}
}
期望結果:1 - 1000
實際結果:1 - x (<1000) 實際輸出結果中存在重複值
結論:已上兩種情況相同針對 private static int value = 1; 都是非線程安全的。
那麼都知道通過synchronized關鍵字可以將get1方法改爲線程安全的。分別在兩段代碼片段中的get1方法加上synchronized關鍵字,但是結果卻又不同了
- 第一段代碼 實例化一個對象,並通過1000個線程調用 get1 方法,synchronized關鍵字起作用的。
- 第二段代碼 通過1000個線程實例化1000個對象,並調用 get1 方法,synchronized關鍵字不起作用。
補充:synchronized關鍵字在多線程情況下針對同一個實例(對象Object)是起作用的。
證明2:private int value = 1; 非線程安全
/**
* 證明 int 聲明屬性爲非線程安全的類
*/
class TT {
private int value = 1; //Integer value = new Integer(1) 同樣
public int get1() throws Exception {
Thread.sleep(10); //值越大重複值越多
return value++;
}
}
/**
* 測試類
*/
public class Test {
public static void main(String[] args) throws Exception {
TT t = new TT(); //註釋3:實例化一個對象,並通過多個線程調用 get1 方法。
for(int i=1; i<=1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(t.get1());
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
}
期望結果:1 - 1000
實際結果:1 - x (<1000) 實際輸出結果中存在重複值
同樣將已上代碼片段稍作調整再次驗證。
註釋3處,實例化對象挪到線程run方法體內
/**
* 證明 int 聲明屬性爲非線程安全的類
*/
class TT {
private int value = 1; //Integer value = new Integer(1) 同樣
public int get1() throws Exception {
Thread.sleep(10); //值越大重複值越多
return value++;
}
}
/**
* 測試類
*/
public class Test {
public static void main(String[] args) throws Exception {
for(int i=1; i<=1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
TT t = new TT(); //註釋3:實例化1000個對象,並調用 get1 方法。
try {
System.out.println(t.get1());
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
}
實際結果:輸出的全部是 1
結論:針對 private int value = 1;
- 第一段代碼 實例化一個對象,並通過1000個線程調用 get1 方法,value值非線程安全。
- 第二段代碼 通過1000個線程實例化1000個對象,並調用 get1 方法,value值線程安全。
補充:在多線程情況下針對同一個實例(對象Object)內的基礎類型聲明的屬性 進行調用是非線程安全的。
總結兩種方式的區別。
- 靜態屬性相對於類(class)是非線程安全的。如上結論無論實例化一個對象,並通過多線程調用方法。還是通過多線程實例化多個對象,調用方法結果是一樣的。
- 一般屬性相對於對象(object)是非線程安全的。如上結論,實例化一個對象,並通過多個線程調用方法獲取屬性值,值是不可靠的。而實通過線程實例化多個對象,並調用方法獲取屬性值,值是可靠的。
具體理論可參考jvm實戰第二章內存管理。