您當前的位置 :云南之窗 > 游戲 >  內容正文
投稿

年后想跳槽?那你必須得這100道面試題

云南之窗 2020-03-29 11:31:19 來源: 閱讀:

碼個蛋(codeegg) 第 816 次推文

碼妞看世界

風中飄動的蘆花

1. Linux自帶多種通訊方式,為什么Android用了Binder

進程: 進程是操作系統的概念. 每當我們執行一個程序時,對于操作系統來講就創建了一個進程. 在這個過程中,伴隨著資源的分配和釋放. 可以認為進程是一個程序的一次執行過程.

進程間通信: 進程用戶空間是相互獨立的,一般而言是不能相互訪問的. 但很多情況下進程間需要互相通信,來完成系統的某項功能. 進程通過與內核及其它進程之間的互相通信來協調它們的行為.

Linux現有進程間通信: 1)管道:在創建時分配一個page大小的內存,緩存區大小比較有限. 2)消息隊列:信息復制兩次,額外的CPU消耗;不合適頻繁或信息量大的通信. 3)共享內存:無須復制,共享緩沖區直接附加到進程虛擬地址空間,速度快;但進程間的同步問題操作系統無法實現,必須各進程利用同步工具解決. 4)套接字:作為更通用的接口,傳輸效率低,主要用于不同機器或跨網絡的通信. 5)信號量:常作為一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源.因此,主要作為進程間以及同一進程內不同線程之間的同步手段. 6)信號:不適用于信息交換,更適用于進程中斷控制,比如非法內存訪問,殺死某個進程等.

對比: 1)從性能的角度(數據拷貝次數) 0次:共享內存 1次:Binder 2次:管道 消息隊列 套接字 從性能角度看,Binder性能僅次于共享內存.

2)從穩定性的角度 Binder基于C/S架構,C和S相對獨立,穩定性較好. 共享內存實現方式復雜,沒有客戶與服務端之別,需要充分考慮到訪問臨界資源的并發同步問題,否則可能會出現死鎖等問題. 從穩定性角度看,Binder優越于共享內存.

3)從安全的角度 傳統Linux的IPC的接收方無法獲得對方進程可靠的UID/PID,從而無法鑒別對方身份. 而Android作為一個開放的開源體系,擁有非常多的開發平臺,App來源甚廣,因此手機的安全顯得額外重要. 對于普通用戶,絕不希望從App商店下載偷窺隱私數據、后臺造成手機耗電等等問題,傳統Linux IPC無任何保護措施,完全由上層協議來確保.

Android為每個安裝好的應用程序分配了自己的UID,故進程的UID是鑒別進程身份的重要標志. 前面提到C/S架構,Android系統中對外只暴露Client端,Client端將任務發送給Server端. Server端會根據權限控制策略判斷UID/PID是否滿足訪問權限,目前權限控制很多時候是通過彈出權限詢問對話框,讓用戶選擇是否運行.

傳統IPC只能由用戶在數據包里填入UID/PID. 另外,可靠的身份標記只有由IPC機制本身在內核中添加. 其次傳統IPC訪問接入點是開放的,無法建立私有通道. 從安全角度,Binder的安全性更高.

4)從語言層面的角度 Linux基于C(面向過程),Android基于Java(面向對象). 而對于Binder恰恰也符合面向對象的思想,將進程間通信轉化為通過對某個Binder對象的引用調用該對象的方法. 而其獨特之處在于Binder對象是一個可以跨進程引用的對象,它的實體位于一個進程中,而它的引用卻遍布于系統的各個進程之中. 可以從一個進程傳給其它進程,讓大家都能訪問同一Server,就像將一個對象或引用賦值給另一個引用一樣. Binder模糊了進程邊界,淡化了進程間通信過程,整個系統仿佛運行于同一個面向對象的程序之中. 從語言層面,Binder更適合基于面向對象語言的Android系統.

5)從公司戰略的角度 Linux內核是開源的系統,所開放源代碼許可協議GPL保護,該協議具有“病毒式感染”的能力. Android之父Andy Rubin對于GPL顯然是不能接受的.

為此,Google巧妙地將GPL協議控制在內核空間. 將用戶空間的協議采用Apache-2.0協議. 同時在GPL協議與Apache-2.0之間的Lib庫中采用BSD授權方法,有效隔斷了GPL的傳染性.

綜合上述5點,可知Binder是Android系統上層進程間通信的不二選擇.

2. Java中用到的線程調度算法是什么

計算機通常只有一個CPU,在任意時刻只能執行一條機器指令,每個線程只有獲得CPU的使用權才能執行指令.所謂多線程的并發運行,其實是指從宏觀上看,各個線程輪流獲得CPU的使用權,分別執行各自的任務.在運行池中,會有多個處于就緒狀態的線程在等待CPU,JAVA虛擬機的一項任務就是負責線程的調度,線程調度是指按照特定機制為多個線程分配CPU的使用權.

有兩種調度模型:分時調度模型和搶占式調度模型。

分時調度模型是指讓所有的線程輪流獲得cpu的使用權,并且平均分配每個線程占用的CPU的時間片這個也比較好理解。

java虛擬機采用搶占式調度模型,是指優先讓可運行池中優先級高的線程占用CPU,如果可運行池中的線程優先級相同,那么就隨機選擇一個線程,使其占用CPU。處于運行狀態的線程會一直運行,直至它不得不放棄CPU。

一般線程調度模式分為兩種——搶占式調度和協同式調度.

搶占式調度指的是每條線程執行的時間、線程的切換都由系統控制,線程的切換不由線程本身決定,系統控制指的是在系統某種運行機制下,可能每條線程都分同樣的執行時間片,也可能是某些線程執行的時間片較長,甚至某些線程得不到執行的時間片。在這種機制下,一個線程的堵塞不會導致整個進程堵塞。

協同式調度指某一線程執行完后主動通知系統切換到另一線程上執行,線程的執行時間由線程本身控制,這種模式就像接力賽一樣,一個人跑完自己的路程就把接力棒交接給下一個人,下個人繼續往下跑。線程的執行時間由線程本身控制,線程切換可以預知,不存在多線程同步問題,但它有一個致命弱點:如果一個線程編寫有問題,運行到一半就一直堵塞,那么可能導致整個系統崩潰。

Java使用的是哪種線程調度模式?此問題涉及到JVM的實現,JVM規范中規定每個線程都有優先級,且優先級越高越優先執行,但優先級高并不代表能獨自占用執行時間片,可能是優先級高得到越多的執行時間片,反之,優先級低的分到的執行時間少但不會分配不到執行時間。JVM的規范沒有嚴格地給調度策略定義,一般Java使用的線程調度是搶占式調度,在JVM中體現為讓可運行池中優先級高的線程擁有CPU使用權,如果可運行池中線程優先級一樣則隨機選擇線程,但要注意的是實際上一個絕對時間點只有一個線程在運行(這里是相對于一個CPU來說),直到此線程進入非可運行狀態或另一個具有更高優先級的線程進入可運行線程池,才會使之讓出CPU的使用權

3. 談談對Java反射的理解

反射 (Reflection) 是 Java 的特征之一,它允許運行中的 Java 程序獲取自身的信息,并且可以操作類或對象的內部屬性。

通過反射,我們可以在運行時獲得程序或程序集中每一個類型的成員和成員的信息。程序中一般的對象的類型都是在編譯期就確定下來的,而 Java 反射機制可以動態地創建對象并調用其屬性,這樣的對象的類型在編譯期是未知的。所以我們可以通過反射機制直接創建對象,即使這個對象的類型在編譯期是未知的。

反射的核心是 JVM 在運行時才動態加載類或調用方法/訪問屬性,它不需要事先(寫代碼的時候或編譯期)知道運行對象是誰。

Java 反射主要提供以下功能:

  • 在運行時判斷任意一個對象所屬的類;

  • 在運行時構造任意一個類的對象;

  • 在運行時判斷任意一個類所具有的成員變量和方法(通過反射甚至可以調用private方法);

  • 在運行時調用任意一個對象的方法

重點:是運行時而不是編譯時

**反射最重要的用途就是開發各種通用框架。**很多框架(比如 Spring)都是配置化的(比如通過 XML 文件配置 Bean),為了保證框架的通用性,它們可能需要根據配置文件加載不同的對象或類,調用不同的方法,這個時候就必須用到反射,運行時動態加載需要加載的對象。

由于反射會額外消耗一定的系統資源,因此如果不需要動態地創建一個對象,那么就不需要用反射。

另外,反射調用方法時可以忽略權限檢查,因此可能會破壞封裝性而導致安全問題。

4.如何停止一個正在運行的線程

使用共享變量的方式 在這種方式中,之所以引入共享變量,是因為該變量可以被多個執行相同任務的線程用來作為是否中斷的信號,通知中斷線程的執行。

使用interrupt方法終止線程 如果一個線程由于等待某些事件的發生而被阻塞,又該怎樣停止該線程呢?這種情況經常會發生,比如當一個線程由于需要等候鍵盤輸入而被阻塞,或者調用Thread.join方法,或者Thread.sleep方法,在網絡中調用ServerSocket.accept方法,或者調用了DatagramSocket.receive方法時,都有可能導致線程阻塞,使線程處于處于不可運行狀態時,即使主程序中將該線程的共享變量設置為true,但該線程此時根本無法檢查循環標志,當然也就無法立即中斷。這里我們給出的建議是,不要使用stop方法,而是使用Thread提供的interrupt方法,因為該方法雖然不會中斷一個正在運行的線程,但是它可以使一個被阻塞的線程拋出一個中斷異常,從而使線程提前結束阻塞狀態,退出堵塞代碼

5. 并發集合與普通集合的區別

在java中有普通集合、同步(線程安全)的集合、并發集合。普通集合通常性能最高,但是不保證多線程的安全性和并發的可靠性。線程安全集合僅僅是給集合添加了 synchronized 同步鎖,嚴重犧牲了性能,而且對并發的效率就更低了,并發集合則通過復雜的策略不僅保證了多線程的安全又提高的并發時的效率。 參考閱讀: ConcurrentHashMap 是線程安全的HashMap的實現,默認構造同樣有initialCapacity 和loadFactor屬性,不過還多了一個 concurrencyLevel屬性,三屬性默認值分別為16、0.75 及 16。其內部使用鎖分段技術,維持這鎖Segment的數組,在Segment數組中又存放著Entity數組,內部hash算法將數據較均勻分布在不同鎖中。

put操作: 并沒有在此方法上加上synchronized,首先對key.hashcode進行hash 操作,得到key的hash 值。hash 操作的算法和 map 也不同,根據此 hash 值計算并獲取其對應的數組中的 Segment 對象(繼承自ReentrantLock),接著調用此Segment對象的put方法來完成當前操作。 ConcurrentHashMap 基于concurrencyLevel劃分出了多個Segment 來對key-value進行存儲,從而避免每次put操作都得鎖住整個數組。在默認的情況下,最佳情況下可允許16 個線程并發無阻塞的操作集合對象,盡可能地減少并發時的阻塞現象。

get(key): 首先對key.hashCode進行hash 操作,基于其值找到對應的Segment 對象,調用其get方法完成當前操作。而 Segment 的 get 操作首先通過 hash 值和對象數組大小減 1 的值進行按位與操作來獲取數組上對應位置的HashEntry。在這個步驟中,可能會因為對象數組大小的改變,以及數組上對應位置的HashEntry 產生不一致性,那么ConcurrentHashMap 是如何保證的? 對象數組大小的改變只有在put操作時有可能發生,由于HashEntry對象數組對應的變量是volatile類型的,因此可以保證如HashEntry 對象數組大小發生改變,讀操作可看到最新的對象數組大小。 在獲取到了 HashEntry 對象后,怎么能保證它及其 next 屬性構成的鏈表上的對象不會改變呢?這點ConcurrentHashMap 采用了一個簡單的方式,即HashEntry 對象中的hash、key、next 屬性都是final 的,這也就意味著沒辦法插入一個HashEntry對象到基于next屬性構成的鏈表中間或末尾。這樣就可以保證當獲取到HashEntry對象后,其基于next 屬性構建的鏈表是不會發生變化的。 ConcurrentHashMap 默認情況下采用將數據分為 16 個段進行存儲,并且 16 個段分別持有各自不同的鎖Segment,鎖僅用于put 和 remove 等改變集合對象的操作,基于volatile 及 HashEntry 鏈表的不變性實現了讀取的不加鎖。這些方式使得ConcurrentHashMap 能夠保持極好的并發支持,尤其是對于讀遠比插入和刪除頻繁的Map而言,而它采用的這些方法也可謂是對于Java內存模型、并發機制深刻掌握的體現。

今日問題:

大家有年后跳槽的心思嗎?

專屬升級社區:《這件事情,我終于想明白了》

(正文已結束)

推薦閱讀:小熊電餅鐺

免責聲明及提醒:此文內容為本網所轉載企業宣傳資訊,該相關信息僅為宣傳及傳遞更多信息之目的,不代表本網站觀點,文章真實性請瀏覽者慎重核實!任何投資加盟均有風險,提醒廣大民眾投資需謹慎!

網站簡介 - 聯系我們 - 營銷服務 - 老版地圖 - 版權聲明 - 網站地圖
Copyright.2002-2019 云南之窗 版權所有 本網拒絕一切非法行為 歡迎監督舉報 如有錯誤信息 歡迎糾正
饿了么商户赚钱 下载北京时时彩赛车网址 宁夏11选五彩票平台 广东26选5预测 河北排列7开奖号码 新浪股票行情查询 青海快三开奖号码 股票开盘价是怎么定 陕西十一选五一定牛走势图 11168期博彩老头 湖北30选五的走势