Java筆試面試題整理第六波(修正版)__Java面試題

來源:互聯網
上載者:User
本系列整理Java相關的筆試面試知識點,其他幾篇文章如下:  Java筆試面試題整理第八波
 Java筆試面試題整理第七波
 Java筆試面試題整理第六波
 Java筆試面試題整理第五波
 Java筆試面試題整理第四波
 Java筆試面試題整理第三波
 Java筆試面試題整理第二波
 Java筆試面試題整理第一波
1、線程池ThreadPool相關 在java.util.concurrent包下,提供了一系列與線程池相關的類。合理的使用線程池,可以帶來多個好處: (1)降低資源消耗。通過重複利用已建立的線程降低線程建立和銷毀造成的消耗; (2)提高響應速度。當任務到達時,任務可以不需要等到線程建立就能立即執行; (3)提高線程的可管理性。線程是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
線程池可以應對突然大爆發量的訪問,通過有限個固定線程為大量的操作服務,減少建立和銷毀線程所需的時間。
與線程執行、線程池相關類的關係如圖:
我們一般通過工具類Executors的靜態方法(如newFixedThreadPool())來擷取ThreadPoolExecutor線程池或靜態方法(如newScheduledThreadPool())來擷取ScheduleThreadPoolExecutor線程池。如下使用: ExecutorService threadpool= Executors.newFixedThreadPool(10); 我們指定了擷取10個數量的固定線程池,Executors中有很多重載的擷取線程池的方法,比如可以通過自訂的ThreadFactory來為每個建立出來的Thread設定更為有意義的名稱。Executors建立線程池的方法內部也就是new出新的ThreadPoolExecutor或ScheduleThreadPoolExecutor,給我們配置了很多預設的設定。如下:
public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, nThreads,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>());    }
上面通過ThreadPoolExecutor的構造方法,為我們建立了一個線程池,很多參數Executors工具類自動為我們配置好了。建立一個ThreadPoolExecutor線程池一般需要以下幾個參數:
public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory,                              RejectedExecutionHandler handler) 
(1)corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會建立一個線程來執行任務,即使其他閒置基本線程能夠執行新任務也會建立線程,等到需要執行的任務數大於線程池基本大小時就不再建立。如果調用了線程池的prestartAllCoreThreads方法,線程池會提前建立並啟動所有基本線程。 (2)maximumPoolSize(線程池最大大小):線程池允許建立的最大線程數。如果隊列滿了,並且已建立的線程數小於最大線程數,則線程池會再建立新的線程執行任務。值得注意的是如果使用了無界的任務隊列這個參數就沒什麼效果。 (3)keepAliveTime(線程活動保持時間):線程池的背景工作執行緒空閑後,保持存活的時間。所以如果任務很多,並且每個任務執行的時間比較短,可以調大這個時間,提高線程的利用率。 (4)TimeUnit(線程活動保持時間的單位):可選的單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS)等。 (5)workQueue(任務隊列):用於儲存等待執行的任務的阻塞隊列。 可以選擇以下幾個阻塞隊列:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue (6)threadFactory:用於設定建立線程的工廠,可以通過線程工廠給每個建立出來的線程設定更有意義的名字。 (7)handler(飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略預設情況下是AbortPolicy,表示無法處理新任務時拋出異常。
我們盡量優先使用Executors提供的靜態方法來建立線程池,如果Executors提供的方法無法滿足要求,再自己通過ThreadPoolExecutor類來建立線程池。
提交任務的兩種方式: (1)通過execute()方法,如:
ExecutorService threadpool= Executors.newFixedThreadPool(10);threadpool.execute(new Runnable(){...});
這種方式提交沒有傳回值,也就不能判斷任務是否被線程池執行成功。
(2)通過submit()方法,如:     
Future<?> future = threadpool.submit(new Runnable(){...});    try {            Object res = future.get();        } catch (InterruptedException e) {            // 處理中斷異常            e.printStackTrace();        } catch (ExecutionException e) {            // 處理無法執行任務異常            e.printStackTrace();        }finally{            // 關閉線程池            executor.shutdown();        }
使用submit 方法來提交任務,它會返回一個Future對象,通過future的get方法來擷取傳回值,get方法會阻塞住直到任務完成,而使用get(long timeout, TimeUnit unit)方法則會阻塞一段時間後立即返回,這時有可能任務沒有執行完。
線程池工作流程分析:(來自參考文章)
從上圖我們可以看出,當提交一個新任務到線程池時,線程池的處理流程如下:
1、首先線程池判斷基本線程池是否已滿(< corePoolSize 。)。沒滿,建立一個背景工作執行緒來執行任務。滿了,則進入下個流程。
2、其次線程池判斷工作隊列是否已滿。沒滿,則將新提交的任務儲存在工作隊列裡。滿了,則進入下個流程。 3、最後線程池判斷整個線程池是否已滿(< maximumPoolSize 。)。沒滿,則建立一個新的背景工作執行緒來執行任務,滿了,則交給飽和策略來處理這個任務。
也就是說,線程池優先要建立出基本線程池大小(corePoolSize)的線程數量,沒有達到這個數量時,每次提交新任務都會直接建立一個新線程,當達到了基本線程數量後,又有新任務到達,優先放入等待隊列,如果隊列滿了,才去建立新的線程(不能超過線程池的最大數maxmumPoolSize)。
關於線程池的配置原則可閱讀參考文章。
ThreadPoolExecutor簡單一實例:
public class BankCount {    public synchronized void addMoney(int money){//存錢        System.out.println(Thread.currentThread().getName() + ">存入:" + money);    }    public synchronized void getMoney(int money){//取錢        System.out.println(Thread.currentThread().getName() + ">取錢:" + money);    }}
測試類別:
public class BankTest {    public static void main(String[] args) {        final BankCount bankCount = new BankCount();        ExecutorService executor = Executors.newFixedThreadPool(10);        executor.execute(new Runnable() {//存錢線程            @Override            public void run() {                int i = 5;                while(i-- > 0){                    bankCount.addMoney(200);                    try {                        Thread.sleep(500);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        });        Future<?> future = executor.submit(new Runnable() {//取錢線程            @Override            public void run() {                int i = 5;                while(i-- > 0){                    try {                        Thread.sleep(500);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    bankCount.getMoney(200);                }            }        });        try {            Object res = future.get();            System.out.println(res);        } catch (InterruptedException e) {            // 處理中斷異常            e.printStackTrace();        } catch (ExecutionException e) {            // 處理無法執行任務異常            e.printStackTrace();        }finally{            // 關閉線程池            executor.shutdown();        }    }}
列印結果如下: pool-1-thread-1>存入:200 pool-1-thread-1>存入:200 pool-1-thread-2>取錢:200 pool-1-thread-1>存入:200 pool-1-thread-2>取錢:200 pool-1-thread-1>存入:200 pool-1-thread-2>取錢:200 pool-1-thread-1>存入:200 pool-1-thread-2>取錢:200 pool-1-thread-2>取錢:200 null
可以看到,列印出來的future.get()擷取的結果為null,這是因為Runnable是沒有傳回值的,需要傳回值要使用Callable,這裡就不再細說了,具體可參考如下文章: http://blog.csdn.net/xiaojin21cen/article/details/41820983
http://icgemu.iteye.com/blog/467848

2、生產者和消費者模型 生產者消費者模型,描述是:有一塊緩衝區作為倉庫,生產者可以將產品放入倉庫,消費者可以從倉庫中取走產品。解決消費者和生產者問題的 核心在於保證同一資源被多個線程並發訪問時的完整性 。一般採用訊號量或加鎖機制解決。下面介紹Java中解決生產者和消費者問題主要三種仿: (1)wait() / notify()、notifyAll() wait和notify方法是Object的兩個方法,因此每個類都會擁有這兩個方法。 wait()方法:使當前線程處於等待狀態,放棄鎖,讓其他線程執行。 notify()方法:喚醒其他等待同一個鎖的線程,放棄鎖,自己處於等待狀態。 如下例子:
/** * 倉庫 */public class Storage {    private static final int MAX_SIZE = 100;//倉庫的最大容量    private List<Object> data = new ArrayList<Object>();//儲存載體    /**     * 生產操作     */    public synchronized void produce(int num){        if(data.size() + num > MAX_SIZE){//如果生產這些產品將超出倉庫的最大容量,則生產操作阻塞            System.out.println("生產操作-->數量:" + num + ",超出倉庫容量,生產阻塞。------庫存:" + data.size());            try {                wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        //到這裡,表示可以正常生產產品        for(int i = 0; i < num; i++){//生產num個產品            data.add(new Object());        }        System.out.println("生產操作-->數量:" + num + ",成功入庫~------庫存:" + data.size());        //生產完產品後,喚醒其他等待消費的線程        notify();    }    /**     * 消費操作     */    public synchronized void consume(int num){        if(data.size() - num < 0){//如果產品數量不足            System.out.println("消費操作-->數量:" + num + ",庫存不足,消費阻塞。------庫存:" + data.size());            try {                wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        //到這裡,表示可以正常消費        for(int i = 0; i < num; i++){//消費num個產品            data.remove(0);        }        System.out.println("消費操作-->數量:" + num + ",消費成功~------庫存:" + data.size());        //消費完產品後,喚醒其他等待生產的線程        notify();    }}
生產者:
public class Producer implements Runnable{    private Storage storage;    private int num;//每次生產多少個    public Producer(Storage sto,int num){        storage = sto;        this.num = num;    }    @Override    public void run() {        storage.produce(num);    }}
消費者:
public class Consumer implements Runnable{    private Storage storage;    private int num;//每次消費多少個    public Consumer(Storage sto,int num){        storage = sto;        this.num = num;    }    @Override    public void run() {        storage.consume(num);    }}
測試類別:
public class StorageTest {    public static void main(String[] args) {        Storage storage = new Storage();        ExecutorService taskSubmit = Executors.newFixedThreadPool(10);    //來使用使用上一節我們總結的線程池知識        //給定4個消費者        taskSubmit.submit(new Consumer(storage, 30));        taskSubmit.submit(new Consumer(storage, 10));        taskSubmit.submit(new Consumer(storage, 20));        //給定6個生產者        taskSubmit.submit(new Producer(storage, 70));        taskSubmit.submit(new Producer(storage, 10));        taskSubmit.submit(new Producer(storage, 20));        taskSubmit.submit(new Producer(storage, 10));        taskSubmit.submit(new Producer(storage, 10));        taskSubmit.submit(new Producer(storage, 10));        taskSubmit.shutdown();    }}
列印結果: 消費操作-->數量:30,庫存不足,消費阻塞。------庫存:0 生產操作-->數量:10,成功入庫~------庫存:10 生產操作-->數量:70,成功入庫~------庫存:80 生產操作-->數量:10,成功入庫~------庫存:90 生產操作-->數量:10,成功入庫~------庫存:100 生產操作-->數量:20,超出倉庫容量,生產阻塞。------庫存:100 消費操作-->數量:10,消費成功~------庫存:90 生產操作-->數量:20,成功入庫~------庫存:110 生產操作-->數量:10,超出倉庫容量,生產阻塞。------庫存:110 消費操作-->數量:20,消費成功~------庫存:90 消費操作-->數量:30,消費成功~------庫存:60 生產操作-->數量:10,成功入庫~------庫存:70
在倉庫中,喚醒我們使用的是notify()而沒有使用notifyAll(),是因為在這裡,如果測試資料設定不當很容易造成死結(比如一下喚醒了所有的生產進程),因為使用wait和notify有一個缺陷:

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.