首頁  >  文章  >  Java  >  Java中關於線程安全與非線程安全詳解

Java中關於線程安全與非線程安全詳解

黄舟
黄舟原創
2017-10-12 10:09:391720瀏覽

這篇文章主要介紹了Java線程安全與非線程安全解析,涉及非線程安全現像模擬以及線程安全的實現等相關內容,需要的朋友可以參考,一起交流學習。

ArrayList和Vector有什麼差別? HashMap和HashTable有什麼差別? StringBuilder和StringBuffer有什麼差別?這些都是Java面試中常見的基礎問題。面對這樣的問題,答案是:ArrayList是非線程安全的,Vector是線程安全的;HashMap是非線程安全的,HashTable是線程安全的;StringBuilder是非線程安全的,StringBuffer是線程安全的。因為這是昨晚剛背的《Java面試題大全》上面寫的。此時如果繼續問:什麼是線程安全?線程安全和非線程安全有什麼區別?分別在什麼情況下使用?這樣一連串的問題,一口老血就噴出來了…

非線程安全的現像模擬

這裡就使用ArrayList和Vector二者來說明。

下面的程式碼,在主線程中new了一個非線程安全的ArrayList,然後開1000個線程分別向這個ArrayList裡面添加元素,每個線程添加100個元素,等所有線程執行完成後,這個ArrayList的size應該是多少?應該是100000個?


public class Main 
{ 
  public static void main(String[] args) 
  { 
    // 进行10次测试 
    for(int i = 0; i < 10; i++) 
    { 
      test(); 
    } 
  } 
  public static void test() 
  { 
    // 用来测试的List 
    List<Object> list = new ArrayList<Object>(); 
    // 线程数量(1000) 
    int threadCount = 1000; 
    // 用来让主线程等待threadCount个子线程执行完毕 
    CountDownLatch countDownLatch = new CountDownLatch(threadCount); 
    // 启动threadCount个子线程 
    for(int i = 0; i < threadCount; i++) 
    { 
      Thread thread = new Thread(new MyThread(list, countDownLatch)); 
      thread.start(); 
    } 
    try 
    { 
      // 主线程等待所有子线程执行完成,再向下执行 
      countDownLatch.await(); 
    } 
    catch (InterruptedException e) 
    { 
      e.printStackTrace(); 
    } 
    // List的size 
    System.out.println(list.size()); 
  } 
} 
class MyThread implements Runnable 
{ 
  private List<Object> list; 
  private CountDownLatch countDownLatch; 
  public MyThread(List<Object> list, CountDownLatch countDownLatch) 
  { 
    this.list = list; 
    this.countDownLatch = countDownLatch; 
  } 
  public void run() 
  { 
    // 每个线程向List中添加100个元素 
    for(int i = 0; i < 100; i++) 
    { 
      list.add(new Object()); 
    } 
    // 完成一个子线程 
    countDownLatch.countDown(); 
  } 
}

上面進行了10次測試(為什麼要測試10次?因為非執行緒安全性並不是每次都會導致問題)。

輸出結果:


99946
100000
100000
100000
99998
99959
100000
99975
100000
99996

上面的輸出結果發現,並不是每次測試結果都是100000,有好幾次測試最後ArrayList的size小於100000,甚至時不時會拋出個IndexOutOfBoundsException異常。 (如果沒有這個現象可以多試幾次)

這就是非執行緒安全性帶來的問題了。上面的程式碼如果用於生產環境,就會有隱憂就會有BUG了。

再用執行緒安全的Vector來進行測試,上面程式碼改變一處,在test()方法中


##

List<Object> list = new ArrayList<Object>();

改變成



List<Object> list = new Vector<Object>();

再執行程式。


輸出結果:



100000
100000
100000
100000
100000
100000
100000
100000
100000
100000

再多跑幾次,發現都是100000,沒有任何問題。因為Vector是執行緒安全的,在多執行緒操作同一個Vector物件時,不會有任何問題。

再換成LinkedList試試,同樣還會出現ArrayList類似的問題,因為LinkedList也是非執行緒安全的。


二者如何取捨


非執行緒安全性是指多執行緒操作同一個物件可能會出現問題。而線程安全則是多線程操作同一個物件不會有問題。


執行緒安全必須要使用很多synchronized關鍵字來同步控制,所以必然會導致效能的降低。


所以在使用的時候,如果是多個執行緒操作同一個對象,那麼使用線程安全的Vector;否則,就使用效率更高的ArrayList。


非執行緒安全性!=不安全

#有人在使用過程中有一個不正確的觀點:我的程式是多線程的,不能使用ArrayList要使用Vector,這樣才安全。


非執行緒安全性並不是多執行緒環境下就不能使用。注意我上面有說到:多執行緒操作同一個物件。注意是同一個物件。例如最上面那個模擬,就是在主執行緒中new的一個ArrayList然後多個執行緒操作同一個ArrayList物件。


如果是每個線程中new一個ArrayList,而這個ArrayList只在這一線程中使用,那麼肯定是沒問題的。


線程安全的實作

#執行緒安全性是透過執行緒同步控制來實現的,也就是synchronized關鍵字。  


在這裡,我用程式碼分別實作了一個非執行緒安全的計數器和執行緒安全的計數器Counter,並對他們分別進行了多執行緒測試。


非線程安全的計數器:



public class Main 
{ 
  public static void main(String[] args) 
  { 
    // 进行10次测试 
    for(int i = 0; i < 10; i++) 
    { 
      test(); 
    } 
  } 
  public static void test() 
  { 
    // 计数器 
    Counter counter = new Counter(); 
    // 线程数量(1000) 
    int threadCount = 1000; 
    // 用来让主线程等待threadCount个子线程执行完毕 
    CountDownLatch countDownLatch = new CountDownLatch(threadCount); 
    // 启动threadCount个子线程 
    for(int i = 0; i < threadCount; i++) 
    { 
      Thread thread = new Thread(new MyThread(counter, countDownLatch)); 
      thread.start(); 
    } 
    try 
    { 
      // 主线程等待所有子线程执行完成,再向下执行 
      countDownLatch.await(); 
    } 
    catch (InterruptedException e) 
    { 
      e.printStackTrace(); 
    } 
    // 计数器的值 
    System.out.println(counter.getCount()); 
  } 
} 
class MyThread implements Runnable 
{ 
  private Counter counter; 
  private CountDownLatch countDownLatch; 
  public MyThread(Counter counter, CountDownLatch countDownLatch) 
  { 
    this.counter = counter; 
    this.countDownLatch = countDownLatch; 
  } 
  public void run() 
  { 
    // 每个线程向Counter中进行10000次累加 
    for(int i = 0; i < 10000; i++) 
    { 
      counter.addCount(); 
    } 
    // 完成一个子线程 
    countDownLatch.countDown(); 
  } 
} 
class Counter 
{ 
  private int count = 0; 
  public int getCount() 
  { 
    return count; 
  } 
  public void addCount() 
  { 
    count++; 
  } 
}

上面的測試程式碼中,開啟1000個線程,每個線程對計數器進行10000次累加,最終輸出結果應該是10000000。


但是上面程式碼中的Counter未進行同步控制,所以非執行緒安全性。


輸出結果:



9963727
9973178
9999577
9987650
9988734
9988665
9987820
9990847
9992305
9972233

稍微修改,把Counter改成執行緒安全的計數器:



class Counter 
{ 
  private int count = 0; 
  public int getCount() 
  { 
    return count; 
  } 
  public synchronized void addCount() 
  { 
    count++; 
  } 
}

上面只是在addCount()方法中加上了synchronized同步控制,就變成一個執行緒安全的計數器了。再執行程序。


輸出結果:



10000000
10000000
10000000
10000000
10000000
10000000
10000000
10000000
10000000
10000000

總結

以上是Java中關於線程安全與非線程安全詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn