1. 程式人生 > >一道JAVA面試,執行緒安全和靜態內部類

一道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裡面的程式碼可以看出,Threadtarget屬性是指向h物件的。

靈光一閃

現在終於知道里面是什麼情況了。靜態內部類是可以擁有多個例項的。因為我們兩個執行緒分別擁有兩個不同的haha物件。同步方法中的synchronized的鎖分別是兩個haha物件的。所以這時候,兩個執行緒根本沒有被鎖住。
所以,假如兩個Thread例項使用同一個haha物件,這個鎖就會有用了!

同步塊比較容易理解,主要就是關注鎖物件。

總結

首先要承認,這是一道不普通的題。我是一邊寫部落格,一邊解決問題的。此前看來對靜態內部類有點誤解了,認為它只能建立一個例項。對比其與非靜態內部類,就可以很容易理解它的異同了。執行緒鎖物件及同步原理也是解決這個題目最關鍵的地方。

有一個問題我一直都不是很明白,為什麼說Arraylist是非執行緒安全的呢?如何從記憶體角度分析執行緒安全問題呢?