一道JAVA面試,執行緒安全和靜態內部類
前言:4月1號去一家網際網路公司面試,做了一份筆試。考察的內容也非常基礎,但是裡面卻充滿著各種各樣的擴充套件。但是這份題我做得並不好,平時用框架什麼的用多了,反而基礎顯得非常不紮實。憑著記憶寫起最後一套題目。記一下,紮實一下自己的基礎。
程式碼
/**
* declaration:
*
* author wenkangqiang
* date 2016年4月1日
*/
public class FankeTest {
static class haha implements Runnable{
public haha(List<String> list ) {
// TODO Auto-generated constructor stub
this.list = (ArrayList<String>)list;
}
@Override
public void run() {
while(log != null){
log = list.remove(0); //報下標錯誤
System.out.println(log);
}
}
private List<String> list;
private String log = "";
}
public static void main(String[] args) {
String[] strs = new String[]{"aa", "bb", "cc"};
ArrayList<String> list = new ArrayList<String>(Arrays.asList(strs));
Thread t1 = new Thread(new haha(list ));
Thread t2 = new Thread(new haha(list));
t1.start();
t2.start();
}
}
題目是要求我們尋找出裡面的錯誤或不妥的地方。
1、執行緒安全:這是比較明顯的,因為ArrayList是非執行緒安全的,所以在多程序進行的時候,而這個有兩個執行緒共享了靜態程式碼。所以執行緒安全問題是非常明顯的。
2、陣列下標溢位異常:list.remove(0);
在發現數組中已經沒有資料的時候,會直接報下標溢位異常IndexOutOfBoundsException()
,並不會返回null
值給log
。
嘗試修改:
增加判空判斷,為迴圈寫一個出口。為了觀看方便,我為兩個執行緒加了名字,並且在run方法中輸出,為了避免處理速度過快而看不到效果,我可以線上程中新增sleep
方法。
while(log != null){
if (list.size() > 0) {
log = list.remove(0);
try {
//放慢執行緒
Thread.currentThread().sleep(1);
System.out.println(
Thread.currentThread().getName() + log);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
在還沒有新增同步機制時,上面的程式碼會容易產生資料錯誤。
OUTPUT:
t2aa
t1aa
t1cc
t2cc
為了去除髒資料,就應該為程式碼新增同步機制。我先嚐試第一種,同步方法。
@Override
public synchronized void run()
我把run方法直接寫成同步方法。但,仍然會出現髒資料!
OUTPUT:
t1aa
t2aa
t2cc
t1cc
這就證明,synchronized
鎖不住這個方法。
在解釋這個問題前,必需先清楚一些同步方法的原理
1、如果是一個非靜態的同步函式的鎖,鎖物件是this物件。
2、如果是靜態的同步函式的鎖,鎖物件是該類的位元組碼物件。
Thread t1 = new Thread(new haha(list));
程式碼中是通過這一種方式去實現執行緒建立的。也就是說,有兩個不同的。
Thread
物件共享著一個Runnable
介面下的靜態例項?這樣說對嗎?做一個測試瞭解一下。
我們在靜態內部類中添加了一個int i
結果讓人很意外,原本認為兩個new haha(list)
會指向同一片記憶體,實際上我錯了。每一個執行緒都享用這一個haha
例項,只有把i
設為static
才能出現我們想要的共享記憶體。
我嘗試使用一種新的方法去建立執行緒,如圖:
我先創建出一個例項h,然後再傳過去給兩個執行緒建立例項。這時候兩個Thread
例項是共享一段程式碼的。根據Thread裡面的程式碼可以看出,Thread
中target
屬性是指向h物件的。
靈光一閃
現在終於知道里面是什麼情況了。靜態內部類是可以擁有多個例項的。因為我們兩個執行緒分別擁有兩個不同的haha物件。同步方法中的synchronized
的鎖分別是兩個haha物件的。所以這時候,兩個執行緒根本沒有被鎖住。
所以,假如兩個Thread
例項使用同一個haha
物件,這個鎖就會有用了!
同步塊比較容易理解,主要就是關注鎖物件。
總結
首先要承認,這是一道不普通的題。我是一邊寫部落格,一邊解決問題的。此前看來對靜態內部類有點誤解了,認為它只能建立一個例項。對比其與非靜態內部類,就可以很容易理解它的異同了。執行緒鎖物件及同步原理也是解決這個題目最關鍵的地方。
有一個問題我一直都不是很明白,為什麼說Arraylist
是非執行緒安全的呢?如何從記憶體角度分析執行緒安全問題呢?