1. 程式人生 > >面試問題總結

面試問題總結

比較 獲取 是我 總結 str 永遠 了解 好的 2個

數據庫篇

int類型用字符串類型查詢是否會走索引

準備語句:

1 2 3 4 5 DROP TABLE ix_test; CREATE TABLE ix_test (id_1 varchar(20) NOT NULL, PRIMARY KEY(id_1)); INSERT INTO ix_test VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11);

mysql會將數字在整形和字符串之間自動轉換!
這樣下面兩條語句的結果是一樣的:
SELECT * FROM ix_test WHERE id_1=1;
SELECT * FROM ix_test WHERE id_1=‘1‘;

但是在索引使用情況方面,結果就完全不一樣了!第一條不使用索引,第二條使用索引!

兩條語句,第一條使用了索引,但是掃描了全表,第二條直接索引到數據,只需要讀取一行!

1 2 3 4 5 6 7 8 9 10 11 12 13 14 mysql> explain select * from ix_test where id_1=1; +----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+ | id | select_type |
table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+ | 1 | SIMPLE | ix_test | index | PRIMARY | PRIMARY | 302 | NULL | 11 | Using
where; Using index |
+----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+ 1 row in set (0.00 sec) mysql> explain select * from ix_test where id_1=‘1‘; +----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+ | 1 | SIMPLE | ix_test | const | PRIMARY | PRIMARY | 302 | const | 1 | Using index | +----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+ 1 row in set (0.01 sec)

但是如果將id_1字段變為整形,後面用整形或者是字符串去匹配都可以使用索引,而且索引直接命中!

所以,多麽坑爹的mysql sql優化器,多麽痛的領悟!

得出結論,對於where後面字段類型為字符串的數字,如果用整形去匹配(就是不用引號引上數字),則不能由索引直接命中,需要全部掃描。

特別需要註意!

線程池的幾個常見參數

核心線程不會被馬上創建,隨著提交而被創建,但是不會被回收

為什麽用線程池

  1. 創建/銷毀線程伴隨著系統開銷,過於頻繁的創建/銷毀線程,會很大程度上影響處理效率

    例如:

    記創建線程消耗時間T1,執行任務消耗時間T2,銷毀線程消耗時間T3

    如果T1+T3>T2,那麽是不是說開啟一個線程來執行這個任務太不劃算了!

    正好,線程池緩存線程,可用已有的閑置線程來執行新任務,避免了T1+T3帶來的系統開銷

  2. 線程並發數量過多,搶占系統資源從而導致阻塞

    我們知道線程能共享系統資源,如果同時執行的線程過多,就有可能導致系統資源不足而產生阻塞的情況

    運用線程池能有效的控制線程最大並發數,避免以上的問題

  3. 對線程進行一些簡單的管理

    比如:延時執行、定時循環執行的策略等

    運用線程池都能進行很好的實現

線程池ThreadPoolExecutor

既然Android中線程池來自於Java,那麽研究Android線程池其實也可以說是研究Java中的線程池

在Java中,線程池的概念是Executor這個接口,具體實現為ThreadPoolExecutor類,學習Java中的線程池,就可以直接學習他了

對線程池的配置,就是對ThreadPoolExecutor構造函數的參數的配置,既然這些參數這麽重要,就來看看構造函數的各個參數吧

ThreadPoolExecutor提供了四個構造函數


//五個參數的構造函數
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)

//六個參數的構造函數-1
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)

//六個參數的構造函數-2
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler)

//七個參數的構造函數
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

我知道你看到這些構造函數和我一樣也是嚇呆了,但其實一共就7種類型,理解起來簡直和理解一周有7天一樣簡單,而且一周有兩天是周末,其實也就只有5天需要了解!相信我,畢竟扯皮,我比較擅長

  • int corePoolSize => 該線程池中核心線程數最大值

    核心線程:

    線程池新建線程的時候,如果當前線程總數小於corePoolSize,則新建的是核心線程,如果超過corePoolSize,則新建的是非核心線程

    核心線程默認情況下會一直存活在線程池中,即使這個核心線程啥也不幹(閑置狀態)。

    如果指定ThreadPoolExecutor的allowCoreThreadTimeOut這個屬性為true,那麽核心線程如果不幹活(閑置狀態)的話,超過一定時間(時長下面參數決定),就會被銷毀掉

    很好理解吧,正常情況下你不幹活我也養你,因為我總有用到你的時候,但有時候特殊情況(比如我自己都養不起了),那你不幹活我就要把你幹掉了

  • int maximumPoolSize

    該線程池中線程總數最大值

    線程總數 = 核心線程數 + 非核心線程數。核心線程在上面解釋過了,這裏說下非核心線程:

    不是核心線程的線程(別激動,把刀放下...),其實在上面解釋過了

  • long keepAliveTime

    該線程池中非核心線程閑置超時時長

    一個非核心線程,如果不幹活(閑置狀態)的時長超過這個參數所設定的時長,就會被銷毀掉

    如果設置allowCoreThreadTimeOut = true,則會作用於核心線程

  • TimeUnit unit

    keepAliveTime的單位,TimeUnit是一個枚舉類型,其包括:

    1. NANOSECONDS : 1微毫秒 = 1微秒 / 1000
    2. MICROSECONDS : 1微秒 = 1毫秒 / 1000
    3. MILLISECONDS : 1毫秒 = 1秒 /1000
    4. SECONDS : 秒
    5. MINUTES : 分
    6. HOURS : 小時
    7. DAYS : 天
  • BlockingQueue<Runnable> workQueue

    該線程池中的任務隊列:維護著等待執行的Runnable對象

    當所有的核心線程都在幹活時,新添加的任務會被添加到這個隊列中等待處理,如果隊列滿了,則新建非核心線程執行任務

    常用的workQueue類型:

    1. SynchronousQueue:這個隊列接收到任務的時候,會直接提交給線程處理,而不保留它,如果所有線程都在工作怎麽辦?那就新建一個線程來處理這個任務!所以為了保證不出現<線程數達到了maximumPoolSize而不能新建線程>的錯誤,使用這個類型隊列的時候,maximumPoolSize一般指定成Integer.MAX_VALUE,即無限大

    2. LinkedBlockingQueue:這個隊列接收到任務的時候,如果當前線程數小於核心線程數,則新建線程(核心線程)處理任務;如果當前線程數等於核心線程數,則進入隊列等待。由於這個隊列沒有最大值限制,即所有超過核心線程數的任務都將被添加到隊列中,這也就導致了maximumPoolSize的設定失效,因為總線程數永遠不會超過corePoolSize

    3. ArrayBlockingQueue:可以限定隊列的長度,接收到任務的時候,如果沒有達到corePoolSize的值,則新建線程(核心線程)執行任務,如果達到了,則入隊等候,如果隊列已滿,則新建線程(非核心線程)執行任務,又如果總線程數到了maximumPoolSize,並且隊列也滿了,則發生錯誤

    4. DelayQueue:隊列內元素必須實現Delayed接口,這就意味著你傳進去的任務必須先實現Delayed接口。這個隊列接收到任務時,首先先入隊,只有達到了指定的延時時間,才會執行任務

  • ThreadFactory threadFactory

    創建線程的方式,這是一個接口,你new他的時候需要實現他的Thread newThread(Runnable r)方法,一般用不上,這是星期六,休息

    但我還是說一句吧(把槍放下...)

    小夥伴應該知道AsyncTask是對線程池的封裝吧?那就直接放一個AsyncTask新建線程池的threadFactory參數源碼吧:

    new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);
        
        public Thread new Thread(Runnable r) {
            return new Thread(r,"AsyncTask #" + mCount.getAndIncrement());
        }
    }
    

    這麽簡單?就給線程起了個名?!對啊,所以說這是星期六啊,別管他了,雖然我已經強迫你們看完了...

  • RejectedExecutionHandler handler

    這玩意兒就是拋出異常專用的,比如上面提到的兩個錯誤發生了,就會由這個handler拋出異常,你不指定他也有個默認的

    拋異常能拋出什麽花樣來?所以這個星期天不管了,一邊去,根本用不上

新建一個線程池的時候,一般只用5個參數的構造函數。

向ThreadPoolExecutor添加任務

那說了這麽多,你可能有疑惑,我知道new一個ThreadPoolExecutor,大概知道各個參數是幹嘛的,可是我new完了,怎麽向線程池提交一個要執行的任務啊?

通過ThreadPoolExecutor.execute(Runnable command)方法即可向線程池內添加一個任務

ThreadPoolExecutor的策略

上面介紹參數的時候其實已經說到了ThreadPoolExecutor執行的策略,這裏給總結一下,當一個任務被添加進線程池時:

  1. 線程數量未達到corePoolSize,則新建一個線程(核心線程)執行任務
  2. 線程數量達到了corePools,則將任務移入隊列等待
  3. 隊列已滿,新建線程(非核心線程)執行任務
  4. 隊列已滿,總線程數又達到了maximumPoolSize,就會由上面那位星期天(RejectedExecutionHandler)拋出異常

常見四種線程池

如果你不想自己寫一個線程池,那麽你可以從下面看看有沒有符合你要求的(一般都夠用了),如果有,那麽很好你直接用就行了,如果沒有,那你就老老實實自己去寫一個吧

Java通過Executors提供了四種線程池,這四種線程池都是直接或間接配置ThreadPoolExecutor的參數實現的,下面我都會貼出這四種線程池構造函數的源碼,各位大佬們一看便知!

來,走起:

CachedThreadPool()

可緩存線程池:

  1. 線程數無限制
  2. 有空閑線程則復用空閑線程,若無空閑線程則新建線程
  3. 一定程序減少頻繁創建/銷毀線程,減少系統開銷

創建方法:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

源碼:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

通過我上面行雲流水談笑風生天馬行空滔滔不絕的對各種參數的說明,這個源碼你肯定一眼就看懂了,想都不用想(下面三種一樣啦)

FixedThreadPool()

定長線程池:

  1. 可控制線程最大並發數(同時執行的線程數)
  2. 超出的線程會在隊列中等待

創建方法:

//nThreads => 最大線程數即maximumPoolSize
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);

//threadFactory => 創建線程的方法,這就是我叫你別理他的那個星期六!你還看!
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory);

源碼:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

2個參數的構造方法源碼,不用我貼你也知道他把星期六放在了哪個位置!所以我就不貼了,省下篇幅給我扯皮

ScheduledThreadPool()

定長線程池:

  1. 支持定時及周期性任務執行。

創建方法:

//nThreads => 最大線程數即maximumPoolSize
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);

源碼:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

SingleThreadExecutor()

單線程化的線程池:

  1. 有且僅有一個工作線程執行任務
  2. 所有任務按照指定順序執行,即遵循隊列的入隊出隊規則

創建方法:

ExecutorService singleThreadPool = Executors.newSingleThreadPool();

源碼:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

還有一個Executors.newSingleThreadScheduledExecutor()結合了3和4,就不介紹了,基本不用。

線程的幾種狀態:

1. 初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。
2. 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的成為“運行”。
線程對象創建後,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取cpu 的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得cpu 時間片後變為運行中狀態(running)。
3.阻塞(BLOCKED):表線程阻塞於鎖。
4.等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)。
5.超時等待(TIME_WAITING):該狀態不同於WAITING,它可以在指定的時間內自行返回。

6. 終止(TERMINATED):表示該線程已經執行完畢。

面試問題總結