天天看點

阿裡面試題-單件模式及相關問題

内推網上投了份履歷,先是電話面試半個多小時,一周後通知face to face面試,郵件裡面時間是1小時,後來面了接近兩個小時,包括linux檔案系統、常用指令、伺服器監控,java方面包括jvm、jms、多線程、并發、常用架構,DB包括隔離級别、鎖、優化等。因為面試崗位是web java進階開發,是以linux、java、DB都有。最後結果還是挂了。

這裡記錄面試中一個由單件模式擴充的題目,當時答對了前半部分,後半部分在最後問面試官的問題中又請教了他,不過下來試驗,發現他的一個答案有錯。當時就覺得有問題,隻是沒有時間細想就結束了。

package com.du.concurrent;

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton(){
        System.out.println("Singleton construct...");
        try {
            Thread.sleep(500);   //線程睡眠0.5s,模拟有些大對象需要長時間construct的過程
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static Singleton getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        System.out.println("Before Singleton construct...");
        Singleton.getInstance();
    }
}
           

普通的單件模式代碼。其中Thread.sleep(500)用來模拟大對象建構過程中,系統排程建構線程交出CPU時間的過程。

然後被問到假如這個對象很大,不希望在類加載的時候建構對象,希望用一種lazy的方式建構。 1. static對象在類加載時建構,是以上述代碼的輸出為:

Singleton construct...
Before Singleton construct...
           
  1. lazy的方式:
    package com.du.concurrent;
    
    public class Singleton {
        private static Singleton instance = null;
        private Singleton(){
            System.out.println("Singleton construct...");
            try {
                Thread.sleep(500);   //線程睡眠0.5s,模拟有些大對象需要長時間construct的過程
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
        public static Singleton getInstance() {
            if(instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            System.out.println("Before Singleton construct...");
            Singleton.getInstance();
        }
    }
               

上述代碼輸出為:

Before Singleton construct...
Singleton construct...
           

再被問到上面代碼有什麼問題,我回答不是線程安全的。然後就需要做同步保護。最直接就是用synchronized保護。不加synchronized的代碼:

package com.du.concurrent;

public class Singleton {
    private static Singleton instance = null;
    private Singleton(){
        System.out.println("Singleton construct...");
        try {
            Thread.sleep(500);   //線程睡眠0.5s,模拟有些大對象需要長時間construct的過程
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public void print(String str) {
        System.out.println(str);
    }

    private static final int THREAD_COUNT = 20;
    public static void main(String[] args) {
        System.out.println("Before Singleton construct...");
        Thread[] threads = new Thread[THREAD_COUNT];
        for(int i = 0; i < THREAD_COUNT; ++i) {
            threads[i] = new Thread(new Runnable() {    
                public void run() {
                    Singleton.getInstance().print("Thread run...");
                }
            });
            threads[i].start();
        }

        if(Thread.activeCount() > 0) {
            Thread.yield();
        }
    }
}
           

輸出結果顯示Singleton建構了多次。加了synchronized保護:

public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
or
public static Singleton getInstance() {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();
            }
        }
        return instance;
    }
           

結果顯示Singleton建構一次。

在用synchronized保護時,面試官提到要講synchronized細化,我了解上述兩種方式效果是一樣的,也就沒有繼續細化的空間,如果隻用synchronized保護instance = new Singleton();這行代碼,非常明顯是達不到效果的。

以上為面試過程中我的答案。最後面試完,問我有沒有什麼問題,我又請教了這個問題。面試官給的答案:1. 同步可以用volatile修飾對象;2. lazy初始化可以用内部類來實作,因為父類雖然是在加載時就初始化了static對象,但是内部類卻是在調用時才初始化。對1,我感覺有問題,因為《深入了解Java虛拟機》中提到,volatile對象需要在更新值時不依賴于其目前的值,但是這裡隻有當對象為null時才new,也即依賴了目前值。但當時繼續下一個問題,來不及細想。對2,則是完全不知道。

問題1的測試代碼:

package com.du.concurrent;

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton(){
        System.out.println("Singleton construct...");
        try {
            Thread.sleep(500);   //線程睡眠0.5s,模拟有些大對象需要長時間construct的過程
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public void print(String str) {
        System.out.println(str);
    }

    private static final int THREAD_COUNT = 20;
    public static void main(String[] args) {
        System.out.println("Before Singleton construct...");
        Thread[] threads = new Thread[THREAD_COUNT];
        for(int i = 0; i < THREAD_COUNT; ++i) {
            threads[i] = new Thread(new Runnable() {    
                public void run() {
                    Singleton.getInstance().print("Thread run...");
                }
            });
            threads[i].start();
        }

        if(Thread.activeCount() > 0) {
            Thread.yield();
        }
    }
}
           

輸出結果顯示的确是construct了多次。

在内部類中初始化:

package com.du.concurrent;

public class Singleton {
    private Singleton(){
        System.out.println("Singleton construct...");
        try {
            Thread.sleep(500);   //線程睡眠0.5s,模拟有些大對象需要長時間construct的過程
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void print(String str) {
        System.out.println(str);
    }

    public static void main(String[] args) {
        System.out.println("Before Singleton construct...");
        Singleton.InSing.instance.print("Lazy construct...");
    }

    static class InSing{
        public static Singleton instance = new Singleton();
    }
}
           

輸出為:

Before Singleton construct...
Singleton construct...
Lazy construct...
           

construct的确是在進入main函數後才執行。

阿裡的java在業界是非常厲害的,很期望能進入阿裡,不過可惜...