1.java面試問題 之 基礎類型屬性(int)是否線程安全?

腦海第一感覺 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)內的基礎類型聲明的屬性 進行調用是非線程安全的。

總結兩種方式的區別。
  1. 靜態屬性相對於類(class)是非線程安全的。如上結論無論實例化一個對象,並通過多線程調用方法。還是通過多線程實例化多個對象,調用方法結果是一樣的。
  2. 一般屬性相對於對象(object)是非線程安全的。如上結論,實例化一個對象,並通過多個線程調用方法獲取屬性值,值是不可靠的。而實通過線程實例化多個對象,並調用方法獲取屬性值,值是可靠的。

具體理論可參考jvm實戰第二章內存管理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章