Android面試之Java中級Plus篇

一隻有交流障礙的醜程式猿發表於2018-03-17

本文是Android面試題整理中的一篇,結合右下角目錄食用更佳,包括:

  • 執行緒
  • 執行緒中的關鍵字
  • 執行緒池
  • 多執行緒中的工具類
  • 程式
  • 類載入
  • 範型和反射

執行緒


1. 什麼是執行緒

執行緒是作業系統能夠進行排程的最小單位,它被包含在程式之中,是程式中的實際運作單位,可以使用多執行緒對進行運算提速。

2. 編寫多執行緒的幾種方式

  1. 一種是繼承Thread類;
  2. 另一種是實現Runnable介面。兩種方式都要通過重寫run()方法來定義執行緒的行為,推薦使用後者,因為Java中的繼承是單繼承,一個類有一個父類,如果繼承了Thread類就無法再繼承其他類了,顯然使用Runnable介面更為靈活。
  3. 實現Callable介面,該介面中的call方法可以線上程執行結束時產生一個返回值

3. 什麼是FutureTask

FutureTask實現了Future介面和Runnable介面,可以對任務進行取消和獲取返回值等操作。

4. 如何強制啟動一個執行緒

做不到,和gc一樣,只能通知系統,具體何時啟動有系統控制

5. 啟用一個執行緒是呼叫run()還是start()方法

啟動一個執行緒是呼叫start()方法,使執行緒所代表的虛擬處理機處於可執行狀態,這意味著它可以由JVM 排程並執行,這並不意味著執行緒就會立即執行

6. 說出執行緒排程和執行緒同步的方法

執行緒排程

  1. wait( ):Object方法,必須在同步程式碼塊或同步方法中使用,使當前執行緒處於等待狀態,釋放鎖
  2. notify ( ):Object方法,和wait方法聯合使用,通知一個執行緒,具體通知哪個由jvm決定,使用不當可能發生死鎖
  3. notifyAll ( ):Object方法,和wait方法聯合使用,通知所有執行緒,具體哪個執行緒獲得執行權jvm決定
  4. sleep( ):使一個正在執行的執行緒處於睡眠狀態,是一個靜態方法,呼叫此方法要處理InterruptedException異常

7. 執行緒同步

  1. Synchronized修飾方法
  2. Synchronized修飾程式碼塊
  3. Lock/ReadWriteLock
  4. ThreadLocal:每個執行緒都有一個區域性變數的副本,互不干擾。一種以空間換時間的方式
  5. java中有很多執行緒安全的容器和方法,可以幫助我們實現執行緒同步:如Collections.synchronizedList()方法將List轉為執行緒同步;用ConurrentHashMap 實現hashmap的執行緒同步。BlockingQueue阻塞佇列也是執行緒同步的,非常適用於生產者消費者模式
  6. 擴充套件:volatile(volatile修飾的變數不會快取在暫存器中,每次使用都會從主存中讀取):保證可見性,不保證原子性,因此不是執行緒安全。在一寫多讀/狀態標誌的場景中使用

8. 什麼是可重入鎖

所謂重入鎖,指的是以執行緒為單位,當一個執行緒獲取物件鎖之後,這個執行緒可以再次獲取本物件上的鎖,而其他的執行緒是不可以的

9. Java中如何停止一個執行緒

  1. Java提供了很豐富的API但沒有為停止執行緒提供API
  2. 可以用volatile 布林變數來退出run()方法的迴圈或者是取消任務來中斷執行緒

10. 一個執行緒執行時發生異常會怎樣

  1. 如果異常沒有被捕獲該執行緒將會停止執行
  2. 可以用UncaughtExceptionHandler來捕獲這種異常

11. 多執行緒共享資料

  1. 使用同一個runnable物件
  2. 使用不同的runnable物件,將同一共享資料例項傳給不同的runnable
  3. 使用不同的runnable物件,將這些Runnable物件作為一個內部類,將共享資料作為成員變數

12. 多執行緒的最佳實踐/好習慣

  1. 給執行緒起個有意義的名字
  2. 避免使用鎖和縮小鎖的範圍
  3. 多用同步輔助類(CountDownLatch、CyclicBarrier、Semaphore)少用wait、notify
  4. 多用併發集合少用同步集合

13. ThreadLocal的設計理念與作用

  1. 供執行緒內的區域性變數,執行緒獨有,不與其他執行緒共享
  2. 適用場景:多執行緒情況下某一變數不需要執行緒間共享,需要各個執行緒間相互獨立

14. ThreadLocal原理,用的時候需要注意什麼

  1. ThreadLocal通過獲得Thread例項內部的ThreadLocalMap來存取資料
  2. ThreadLocal例項本身作為key值
  3. 如果使用執行緒池,Threadlocal可能是上一個執行緒的值,需要我們顯示的控制
  4. ThreadLocal的key雖然採用弱引用,但是仍然可能造成記憶體洩漏(key為null,value還有值)
    擴充套件:Android中的ThreadLocal實現略有不同,使用Thread例項中的是陣列存值,通過ThreadLocal例項計算一個唯一的hash確定下標。

15. 執行緒的基本狀態及狀態之間的關係

Android面試之Java中級Plus篇

16. 如果同步塊內的執行緒丟擲異常會發生什麼

  1. 執行緒內的異常可以捕獲,如果沒有捕獲,該執行緒會停止執行退出
  2. 不論是正常退出還是異常退出,同步塊中的鎖都會釋放

17. 什麼是死鎖(deadlock)

兩個執行緒互相等待對方釋放資源才能繼續執行下去,這個時候就形成了死鎖,誰都無法繼續執行(或者多個執行緒迴圈等待)

18. N個執行緒訪問N個資源,如何避免死鎖

以同樣的順序加鎖和釋放鎖

19. 為什麼應該在迴圈中檢查等待條件

處於等待狀態的執行緒可能會收到錯誤警報和偽喚醒,如果不在迴圈中檢查等待條件,程式就會在沒有滿足結束條件的情況下退出

20. Java中的同步集合與併發集合有什麼區別

  1. 同步集合與併發集合都為多執行緒和併發提供了合適的執行緒安全的集合
  2. 併發集合效能更高

21. Java中活鎖和死鎖有什麼區別

這是上題的擴充套件,活鎖和死鎖類似,不同之處在於處於活鎖的執行緒或程式的狀態是不斷改變的,活鎖可以認為是一種特殊的飢餓。一個現實的活鎖例子是兩個 人在狹小的走廊碰到,兩個人都試著避讓對方好讓彼此通過,但是因為避讓的方向都一樣導致最後誰都不能通過走廊。簡單的說就是,活鎖和死鎖的主要區別是前者程式的狀態可以改變但是卻不能繼續執行

22. 怎麼檢測一個執行緒是否擁有鎖

java.lang.Thread中有一個方法叫holdsLock(),它返回true如果當且僅當當前執行緒擁有某個具體物件的鎖

23. Java中ConcurrentHashMap的併發度是什麼

ConcurrentHashMap把實際map劃分成若干部分來實現它的可擴充套件性和執行緒安全。這種劃分是使用併發度獲得的,它是 ConcurrentHashMap類建構函式的一個可選引數,預設值為16,這樣在多執行緒情況下就能避免爭用

24. 什麼是阻塞式方法

阻塞式方法是指程式會一直等待該方法完成期間不做其他事情,ServerSocket的accept()方法就是一直等待客戶端連線。這裡的阻塞是 指呼叫結果返回之前,當前執行緒會被掛起,直到得到結果之後才會返回。此外,還有非同步和非阻塞式方法在任務完成前就返回。

25. 多執行緒中的忙迴圈是什麼

忙迴圈就是程式設計師用迴圈讓一個執行緒等待,不像傳統方法wait(), sleep() 或 yield() 它們都放棄了CPU控制,而忙迴圈不會放棄CPU,它就是在執行一個空迴圈。這麼做的目的是為了保留CPU快取,在多核系統中,一個等待執行緒醒來的時候可 能會在另一個核心執行,這樣會重建快取。為了避免重建快取和減少等待重建的時間就可以使用它了。

26. 如何保證多執行緒下 i++ 結果正確

可以使用synchronized保證原子性,也可以使用AtomicInteger類
擴充套件:volatile只能保證可見性,不能保證原子性,因此不行

27. 簡述Java中具有哪幾種粒度的鎖

Java中可以對類、物件、方法或是程式碼塊上鎖

同步方法和同步程式碼塊的對比

  1. 同步程式碼塊可以指定更小的粒度
  2. 同步程式碼塊可以給指定例項加鎖

28. 類鎖和物件鎖

類鎖其實時一種特殊的物件鎖,它鎖的其實時類對應的class物件

執行緒中的關鍵字和類


0. sleep和wait方法的對比

  1. 兩個方法都是暫停執行緒,釋放cpu資源給其他執行緒
  2. sleep是Thread的靜態方法,wait是Object的方法。
  3. sleep使執行緒進入阻塞狀態;wait使執行緒進入等待狀態,靠其他執行緒notify或者notifyAll來改變狀態
  4. sleep可以在任何地方使用,必須捕獲異常;而wait必須在同步方法或者同步塊中使用,否則會丟擲執行時異常
  5. 最重要的:sleep繼續持用鎖,wait釋放鎖 擴充套件:yield停止當前執行緒,讓同優先順序或者優先順序高的執行緒先執行(但不會釋放鎖);join方法在某一個執行緒的執行過程中呼叫另一個執行緒執行,等到被呼叫的執行緒執行結束後,再繼續執行當前執行緒

1. 執行緒的sleep()方法和yield()方法有什麼區別

  1. sleep方法使當前執行緒阻塞指定時間,隨後進入就緒狀態
  2. yield方法使當前執行緒進入就緒狀態,讓同優先順序或者更高優先順序的執行緒先執行
  3. sleep方法會丟擲interruptedException

2. 為什麼wait, notify 和 notifyAll這些方法不在thread類裡面

JAVA提供的鎖是物件級的而不是執行緒級的,每個物件都有鎖,通 過執行緒獲得。如果執行緒需要等待某些鎖那麼呼叫物件中的wait()方法就有意義了。如果wait()方法定義在Thread類中,執行緒正在等待的是哪個鎖 就不明顯了

3. 為什麼wait和notify方法要在同步塊中呼叫

  1. java規定必須在同步塊中,不在同步塊中會丟擲異常
  2. 如果不在同步塊中,有可能notify在執行的時候,wait沒有收到陷入死鎖

4. synchronized關鍵字的用法

synchronized 用於執行緒同步

  1. 可以修飾方法
  2. 可以修飾程式碼塊
  3. 當持有的鎖是類時,那麼所有例項物件呼叫該方法或者程式碼塊都會被鎖

5. synchronized 在靜態方法和普通方法的區別

  1. synchronized修飾靜態方法時,鎖是類,所有的物件例項用同一把鎖
  2. 修飾普通方法時,鎖是類的例項

6. 當一個執行緒進入一個物件的synchronized方法A之後,其它執行緒是否可進入此物件的synchronized方法B?

不能。其它執行緒只能訪問該物件的非同步方法。第一個執行緒持有了物件鎖,第二個執行緒的同步方法也需要該物件的鎖才能執行,只能在鎖池中等待了。

7. Java中的volatile 變數是什麼

  1. volatile是一個修飾符,只能修飾成員變數
  2. volatile保證了變數的可見性(A執行緒的改變,B執行緒馬上可以獲取到)
  3. volatile禁止進行指令重排序

8. 寫一個雙檢鎖的單例

private static volatile Singleton instance;  

private Singleton(){}  
    
public Singleton getInstance(
if(singleton == null){
  synchronized(Singleton.class){
  if(singleton == null){
  singleton = new Singleton();
  }
  }
}
return sinlgeton;
)
複製程式碼

9. 單例的DCL方式下,那個單例的私有變數要不要加volatile關鍵字,這個關鍵字有什麼用

  1. 要加
  2. 兩個執行緒同時訪問雙檢鎖,有可能指令重排序,執行緒1初始化一半,切換到執行緒2;因為初始化不是一個原子操作,此時執行緒2讀到不為null直接使用,但是因為還沒有初始化完成引起崩潰

10. Synchronized 和Lock\ReadWriteLock的區別

  1. Synchronized時java關鍵字,Lock/ReadWriteLock介面,它們都是可重入鎖
  2. Synchronized由虛擬機器控制,不需要使用者去手動釋放鎖,執行完畢後自動釋放;而Lock是使用者顯示控制的,要使用者去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。
  3. Lock可以用更多的方法,比如tryLock()拿到鎖返回true,否則false;tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間;Lock有lockInterruptibly()方法,是可中斷鎖
  4. ReentrantLock可以實現公平鎖(等得久的先執行)
  5. ReadWriteLock是一個介面,ReentrantReadWriteLock是它的一個實現,將對一個資源(比如檔案)的訪問分成了2個鎖,一個讀鎖和一個寫鎖,提高了讀寫效率。

11. LockSupport

LockSupport是JDK中比較底層的類,用來建立鎖和其他同步工具類的基本執行緒阻塞原語

park 方法獲取許可。許可預設是被佔用的,呼叫park()時獲取不到許可,所以進入阻塞狀態 unpark 方法頒發許可

12. ReadWriteLock

  1. 讀寫分離的鎖,可以提升效率
  2. 讀讀能共存,讀寫、寫寫不能共存

13. 可重入鎖(RetrantLock)實現原理

  1. RetrantLock 是通過CAS和AQS實現的
  2. CAS(Compare And Swap):三個引數,一個當前記憶體值V、舊的預期值A、即將更新的值B,當且僅當預期值A和記憶體值V相同時,將記憶體值修改為B並返回true,否則什麼都不做,並返回false。原子性操作
  3. RetrantLock內部有一個AbstractQueuedSynchronizer例項,AbstractQueuedSynchronizer是一個抽象類,RetrantLock中有兩種對他的實現,一種是公平鎖,一種是非公平鎖
  4. 在lock時,呼叫一個CAS的方法compareAndSet來將state設定為1,state是一個volitale的變數,並將當前執行緒和鎖繫結
  5. 當compareAndSet失敗時,嘗試獲取鎖:如果和鎖繫結的執行緒時當前執行緒,state+1
  6. 如果獲取鎖失敗,將其加入到佇列中等待,從而保證了併發執行的操作變成了序列
  7. 擴充套件:公平鎖和非公平鎖的區別:非公平鎖無視佇列,直接檢視當前可不可以拿到鎖;公平鎖會先檢視佇列,佇列非空的話會加入佇列

14. Others

synchronized 的實現原理以及鎖優化?:Monitor
volatile 的實現原理?:記憶體屏障
CAS?CAS 有什麼缺陷,如何解決?CompareAndSwap,通過cpu指令實現的
AQS :AbstractQueueSynchronizer,是ReentrantLock一個內部類
如何檢測死鎖?怎麼預防死鎖?:死鎖必須滿足四個條件,破壞任意一個條件都可以解除死鎖
Fork/Join框架

執行緒池


0. 什麼是執行緒池(thread pool)

  1. 頻繁的建立和銷燬物件很耗費資源,所以java引入了執行緒池。Java 5+中的Executor介面定義一個執行執行緒的工具。它的子型別即執行緒池介面是ExecutorService。
  2. Executors 是一個工具類,可以幫我們生成一些特性的執行緒池
newSingleThreadExecutor:建立一個單執行緒化的Executor,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。
newFixedThreadPool:建立一個指定工作執行緒數量的執行緒池。每當提交一個任務就建立一個工作執行緒,如果工作執行緒數量達到執行緒池初始的最大數,則將提交的任務存入到池佇列中。
newCachedThreadPool:建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
newScheduleThreadPool:建立一個定長的執行緒池,而且支援定時的以及週期性的任務執行,支援定時及週期性任務執行。
複製程式碼
  1. 我們常用的ThreadPoolExecutor實現了ExecutorService介面,以下是原理和引數說明
原理:
step1.呼叫ThreadPoolExecutor的execute提交執行緒,首先檢查CorePool,如果CorePool內的執行緒小於CorePoolSize,新建立執行緒執行任務。
step2.如果當前CorePool內的執行緒大於等於CorePoolSize,那麼將執行緒加入到BlockingQueue。
step3.如果不能加入BlockingQueue,在小於MaxPoolSize的情況下建立執行緒執行任務。
step4.如果執行緒數大於等於MaxPoolSize,那麼執行拒絕策略。

引數說明:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

corePoolSize 核心執行緒池大小
maximumPoolSize 執行緒池最大容量大小
keepAliveTime 執行緒池空閒時,執行緒存活的時間
TimeUnit 時間單位
ThreadFactory 執行緒工廠
BlockingQueue任務佇列
RejectedExecutionHandler 執行緒拒絕策略


擴充套件:ThreadPoolExecutor 的submit和excute方法都能執行任務,有什麼區別?
1. 入參不同:excute只能接受Runnable,submit可以接受Runnable和Callable
2. submit有返回值
3. 在異常處理時,submit可以通過Future.get捕獲丟擲的異常

複製程式碼

1. 執行緒池如何調優,最大數目如何確認

  1. 執行緒池的調優優根據具體情況具體分析,儘量使系統資源利用率最大
  2. 例如如果cpu效率明顯高於IO,那麼就應該建立更多執行緒提高cpu利用率,避免io等待(參考1參考2
  3. Android中最大數目可以是:cpu數目*2+1,但也要根據具體場景,例如picaso會根據網路狀況調整最大數目(參考

2. 如果你提交給ThreadPoolExcuter任務時,執行緒池佇列已滿,這時會發生什麼

1.如果還沒達到最大執行緒數,則新建執行緒 2.如果已經達到最大執行緒數,交給RejectExecutionHandler處理。 3.如果沒有設定自定義RejectExecutionHandler,則丟擲RejectExecutionExcuption

3. 執行緒池的用法與優勢

優勢: 實現對執行緒的複用,避免了反覆建立及銷燬執行緒的開銷;使用執行緒池統一管理執行緒可以減少併發執行緒的數目,而執行緒數過多往往會線上程上下文切換上以及執行緒同步上浪費過多時間。

用法: 我們可以呼叫ThreadPoolExecutor的某個構造方法來自己建立一個執行緒池。但通常情況下我們可以使用Executors類提供給我們的靜態工廠方法來更方便的建立一個執行緒池物件。建立了執行緒池物件後,我們就可以呼叫submit或者excute方法提交任務到執行緒池中去執行了;執行緒池使用完畢後我們要記得呼叫shutdown方法來關閉它。

多執行緒中的工具類


0. Java併發程式設計:CountDownLatch、CyclicBarrier(柵欄)和Semaphore(訊號量)

  1. CountDownLatch:利用它可以實現類似計數器的功能。比如有一個任務A,它要等待其他4個任務執行完畢之後才能執行,此時就可以利用CountDownLatch來實現這種功能了
public class Test {
     public static void main(String[] args) {   
         final CountDownLatch latch = new CountDownLatch(2);
          
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子執行緒"+Thread.currentThread().getName()+"正在執行");
                    Thread.sleep(3000);
                    System.out.println("子執行緒"+Thread.currentThread().getName()+"執行完畢");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
          
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子執行緒"+Thread.currentThread().getName()+"正在執行");
                     Thread.sleep(3000);
                     System.out.println("子執行緒"+Thread.currentThread().getName()+"執行完畢");
                     latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
          
         try {
             System.out.println("等待2個子執行緒執行完畢...");
            latch.await();
            System.out.println("2個子執行緒已經執行完畢");
            System.out.println("繼續執行主執行緒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
     }
}
複製程式碼
  1. CyclicBarrier: 實現讓一組執行緒等待至某個狀態之後再全部同時執行
public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("執行緒"+Thread.currentThread().getName()+"正在寫入資料...");
            try {
                Thread.sleep(5000);      //以睡眠來模擬寫入資料操作
                System.out.println("執行緒"+Thread.currentThread().getName()+"寫入資料完畢,等待其他執行緒寫入完畢");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有執行緒寫入完畢,繼續處理其他任務...");
        }
    }
}

擴充套件(CyclicBarrier和CountdownLatch的區別):1.CountdownLatch等待幾個任務執行完畢,CyclicBarrier等待達到某個狀態;2.CyclicBarrier可以呼叫reset,迴圈使用;3.CyclicBarrier可以有含Runnable的構造方法,當達到某一狀態時執行某一任務。
複製程式碼
  1. Semaphore:Semaphore可以控同時訪問的某個資源的執行緒個數
public class Test {
    public static void main(String[] args) {
        int N = 8;            //工人數
        Semaphore semaphore = new Semaphore(5); //機器數目
        for(int i=0;i<N;i++)
            new Worker(i,semaphore).start();
    }
     
    static class Worker extends Thread{
        private int num;
        private Semaphore semaphore;
        public Worker(int num,Semaphore semaphore){
            this.num = num;
            this.semaphore = semaphore;
        }
         
        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("工人"+this.num+"佔用一個機器在生產...");
                Thread.sleep(2000);
                System.out.println("工人"+this.num+"釋放出機器");
                semaphore.release();           
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
複製程式碼

1. java中的訊號量(Semaphore)

  1. Semaphore可以控制當前資源被訪問的執行緒個數,超過最大個數後執行緒處於阻塞等待狀態
  2. 當執行緒個數指定為1時,可以當鎖使用

2. 怎麼實現所有執行緒在等待某個事件的發生才會去執行

所有執行緒需要阻塞等待,並且觀察到事件狀態改變滿足條件時自動執行,可以用以下方法實現

  1. 閉鎖CountDownLatch:閉鎖是典型的等待事件發生的同步工具類,將閉鎖的初始值設定1,所有執行緒呼叫await方法等待,當事件發生時呼叫countDown將閉鎖值減為0,則所有await等待閉鎖的執行緒得以繼續執行。
  2. 阻塞佇列BlockingQueue:所有等待事件的執行緒嘗試從空的阻塞佇列獲取元素,將阻塞,當事件發生時,向阻塞佇列中同時放入N個元素(N的值與等待的執行緒數相同),則所有等待的執行緒從阻塞佇列中取出元素後得以繼續執行。
  3. 訊號量Semaphore:設定訊號量的初始值為等待的執行緒數N,一開始將訊號量申請完,讓剩餘的訊號量為0,待事件發生時,同時釋放N個佔用的訊號量,則等待訊號量的所有執行緒將獲取訊號量得以繼續執行。

3. 生產者-消費者實現之阻塞佇列

  1. 擴充套件:通過sychronized關鍵字實現
  2. 阻塞佇列的特徵是當取或放元素是,佇列不滿足條件(比如佇列為空時進行取操作)可以阻塞等待,知道滿足條件
public class BlockingQueueTest {
 private int size = 20;
 private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(size); 
 public static void main(String[] args) { 
 BlockingQueueTest test = new BlockingQueueTest();
 Producer producer = test.new Producer();
 Consumer consumer = test.new Consumer(); 
 producer.start();
 consumer.start(); 
 }
 class Consumer extends Thread{ 
 @Override public void run() {
 while(true){
 try { 
      //從阻塞佇列中取出一個元素 
      queue.take(); 
      System.out.println("佇列剩餘" + queue.size() + "個元素"); 
      } catch (InterruptedException e) { 
      } } } 
} 
 class Producer extends Thread{
       @Override public void run() {
        while (true) {
         try { 
         //向阻塞佇列中插入一個元素 
         queue.put(1); 
         System.out.println("佇列剩餘空間:" + (size - queue.size())); 
         } catch (InterruptedException e) {} }} 
}
}
複製程式碼

4. ArrayBlockingQueue, CountDownLatch類的作用

  1. ArrayBlockingQueue:一個基於陣列實現的阻塞佇列,它在構造時需要指定容量。當試圖向滿佇列中新增元素或者從空佇列中移除元素時,當前執行緒會被阻塞。
  2. CountDownLatch:同步計數器,是一個執行緒工具類,可以讓一個或幾個執行緒等待其他執行緒

5. Condition

Condition是一個介面,有await和signal方法,和Object的wait、notify類似 Condition 通過lock獲得:Condition condition = lock.newCondition(); 相對於Object的wait、notify,Condition的控制更加靈活,可以滿足喚起某一執行緒的目的

程式


0. 程式的三個狀態

  1. 就緒狀態:獲得CPU排程時由 就緒狀態 轉換為 執行狀態
  2. 執行狀態:CPU時間片用完了由 執行狀態 轉換為 就緒狀態 執行狀態
  3. 阻塞狀態:因等待某個事件發生而進入 阻塞狀態,事件發生後由 阻塞狀態 轉換為 就緒狀態

1. 程式的同步和互斥

  1. 互斥:兩個程式由於不能同時使用同一臨界資源,只能在一個程式使用完了,另一程式才能使用,這種現象稱為程式間的互斥。
  2. 對於互斥的資源,A程式到達了該點後,若此時B程式正在對此資源進行操作,則A停下來,等待這些操作的完成再繼續操作。這就是程式間的同步

2. 死鎖產生的必要條件

  1. 互斥:一個資源一次只能被一個程式所使用,即是排它性使用
  2. 不剝奪條件:一個資源僅能被佔有它的程式所釋放,而不能被別的程式強佔
  3. 請求與保持條件:程式已經保持了至少一個資源,但又提出了新的資源要求,而該資源又已被其它程式佔有,此時請求程式阻塞,但又對已經獲得的其它資源保持不放
  4. 環路等待條件:當每類資源只有一個時,在發生死鎖時,必然存在一個程式—資源的環形鏈

類載入


0. 描述一下JVM載入class檔案的原理機制

類載入器的作用是根據指定全限定名稱將class檔案載入到JVM記憶體中,並轉為Class物件。

載入器的種類

  1. 啟動類載入器(根載入器 Bootstrap ClassLoader):由native程式碼實現,負責將存放在<JAVA_HOME>\lib目錄或-Xbootclasspath引數指定的路徑中的類庫載入到記憶體中
  2. 擴充套件載入器(Extension ClassLoader):java語言實現,父載入器是Bootstrap,:負責載入<JAVA_HOME>\lib\ext目錄或java.ext.dirs系統變數指定的路徑中的所有類庫。
  3. 應用程式類載入器(Application ClassLoader):java實現,負責載入使用者類路徑(classpath)上的指定類庫,我們可以直接使用這個類載入器。一般情況,如果我們沒有自定義類載入器預設就是用這個載入器。
  4. 自定義類載入器:有時為了安全會將類加密,或者從遠端(伺服器)載入類 ,這個時候就需要自定義類載入器。自定義通過繼承ClassLoader類實現,loadClass方法已經實現了雙親委派模式,當父類沒有載入成功時,呼叫當前類的findclass方法,所以我們一般重寫該方法。

載入過程

  1. 類載入器採用雙親委派模型進行載入:每次通過先委託父類載入器載入,當父類載入器無法載入時,再自己載入。
  2. 類的生命週期可以分為七個階段:載入 -> 連線(驗證 -> 準備*(為靜態變數分配記憶體並設定預設的初始值)* -> 解析*(將符號引用替換為直接引用)*)-> 初始化 -> 使用 -> 解除安裝

1. 類載入為什麼要使用雙親委派模式,有沒有什麼場景是打破了這個模式

  1. 使用雙親委派模式,保證只載入一次該類
  2. 我們可以使用自定義的類載入器載入同名類,這樣就阻止了系統雙親委派模式的載入

2. ClassLoader的隔離問題

  1. JVM 及 Dalvik 對類唯一的識別是 ClassLoader id + PackageName + ClassName
  2. 兩個相同的類可能因為兩個ClassLoader載入而不相容

反射和範型


0. 反射的原理和作用

  1. 通過類的class物件類獲得類的各種資訊,建立對應的物件或者呼叫方法
  2. App的動態載入或者Android中呼叫其他物件private方法,都需要反射

1. 類物件的獲取方式

  1. String.class:不執行靜態塊和動態構造塊
  2. "hello".getClass();:執行靜態塊和動態構造塊
  3. Class.forName("java.lang.String");:執行靜態塊,不執行動態構造塊

2. 如何通過反射建立物件

  1. String.class.newInstance();
  2. String.class.getConstrutor(Stirng.class).newInstance("hello word");

3. 如何通過反射獲取和設定物件私有欄位的值

  1. 通過類物件的getDeclaredField()方法獲得(Field)物件
  2. 呼叫Field物件的setAccessible(true)方法將其設定為可訪問
  3. 通過get/set方法來獲取/設定欄位的值

4. 通過反射呼叫物件的方法

  1. 通過類物件的getMethod方法獲得Method物件
  2. 呼叫物件的invoke()方法

5. 範型

  1. 範型可以用於類定義和方法定義
  2. 範型的實現是通過擦除實現的,也就是說編譯之後範型資訊會被擦出

6. 萬用字元

  1. 萬用字元有兩種用法:?extends A 和 ? super A
  2. ?extends A 表示?的上界是A,具體什麼型別並不清楚,適合於獲取,獲取到的一定是A型別
  3. ? super A 表示?的下界是A,具體什麼型別並不清楚,適合於插入,一定可以插入A型別

7. 註解(Annotation)

註解分為三種:原始碼級別(source),類檔案級別(class)或者執行時級別(runtime);butternife是類檔案級別 參考:https://blog.csdn.net/javazejian/article/details/71860633 https://blog.csdn.net/u013045971/article/details/53509237
https://www.cnblogs.com/likeshu/p/5526187.html

參考資料

Java面試題全集(上)

http://www.jcodecraeer.com/a/chengxusheji/java/2015/0206/2421.html

相關文章