1. 排序鍊表
給你鍊表的頭結點 head ,請將其按升序排列並返回排序後的鍊表 。

實例1:
輸入:head = [4,2,1,3]輸出:[1,2,3,4]實例2:
輸入:head = [-1,5,3,4,0]輸出:[-1,0,3,4,5]這道題可以用雙指針+歸併排序算法解決,主要以下四個步驟:
快慢指針法,遍歷鍊表找到中間節點
中間節點切斷鍊表
分別用歸併排序排左右子鍊表
合併子鍊表
完整代碼如下:
class Solution { public ListNode sortList(ListNode head) { //如果鍊表為空,或者只有一個節點,直接返回即可,不用排序 if (head == null || head.next == null) return head; //快慢指針移動,以尋找到中間節點 ListNode slow = head; ListNode fast = head; while(fast.next!=null && fast.next.next !=null){ fast = fast.next.next; slow = slow.next; } //找到中間節點,slow節點的next指針,指向mid ListNode mid = slow.next; //切斷鍊表 slow.next = null; //排序左子鍊表 ListNode left = sortList(head); //排序左子鍊表 ListNode right = sortList(mid); //合併鍊表 return merge(left,right); } public ListNode merge(ListNode left, ListNode right) { ListNode head = new ListNode(0); ListNode temp = head; while (left != null && right != null) { if (left.val <= right.val) { temp.next = left; left = left.next; } else { temp.next = right; right = right.next; } temp = temp.next; } if (left != null) { temp.next = left; } else if (right != null) { temp.next = right; } return head.next; }}2. 對稱與非對稱加密算法的區別先複習一下相關概念:
密文:明文被加密算法加密之後,會變成密文,以確保數據安全。密鑰:是一種參數,它是在明文轉換為密文或將密文轉換為明文的算法中輸入的參數。密鑰分為對稱密鑰與非對稱密鑰。對稱加密算法:加密和解密使用相同密鑰的加密算法。常見的對稱加密算法有 AES、3DES、DES、RC5、RC6 等。

非對稱加密算法:非對稱加密算法需要兩個密鑰(公開密鑰和私有密鑰)。公鑰與私鑰是成對存在的,如果用公鑰對數據進行加密,只有對應的私鑰才能解密。主要的非對稱加密算法有:RSA、Elgamal、DSA、D-H、ECC。
3. TCP如何保證可靠性首先,TCP 的連接是基於三次握手,而斷開則是四次揮手。確保連接和斷開的可靠性;其次,TCP 的可靠性,還體現在有狀態;TCP 會記錄哪些數據發送了,哪些數據被接受了,哪些沒有被接受,並且保證數據包按序到達,保證數據傳輸不出差錯;再次,TCP 的可靠性,還體現在可控制。它有報文校驗、ACK 應答、超時重傳(發送方)、失序數據重傳(接收方)、丟棄重複數據、流量控制(滑動窗口)和擁塞控制等機制。4. 聊聊五種 IO 模型4.1 阻塞 IO 模型假設應用程序的進程發起 IO 調用,但是如果內核的數據還沒準備好的話,那應用程序進程就一直在阻塞等待,一直等到內核數據準備好了,從內核拷貝到用戶空間,才返回成功提示,此次 IO 操作,稱之為阻塞 IO。
4.2 非阻塞 IO 模型如果內核數據還沒準備好,可以先返回錯誤信息給用戶進程,讓它不需要等待,而是通過輪詢的方式再來請求。這就是非阻塞 IO,流程圖如下:
4.3 IO 多路復用模型IO 多路復用之 select
應用進程通過調用 select 函數,可以同時監控多個 fd,在 select 函數監控的 fd 中,只要有任何一個數據狀態準備就緒了,select 函數就會返回可讀狀態,這時應用進程再發起 recvfrom 請求去讀取數據。

select 有幾個缺點:
最大連接數有限,在 Linux 系統上一般為 1024。select函數返回後,是通過遍歷 fdset,找到就緒的描述符 fd。IO 多路復用之 epoll
為了解決 select 存在的問題,多路復用模型 epoll 誕生,它採用事件驅動來實現,流程圖如下:

epoll 先通過 epoll_ctl() 來註冊一個 fd(文件描述符),一旦基於某個 fd 就緒時,內核會採用回調機制,迅速激活這個 fd,當進程調用 epoll_wait() 時便得到通知。這裡去掉了遍歷文件描述符的坑爹操作,而是採用監聽事件回調的機制。這就是 epoll 的亮點。
4.4 IO 模型之信號驅動模型信號驅動 IO 不再用主動詢問的方式去確認數據是否就緒,而是向內核發送一個信號(調用 sigaction 的時候建立一個 SIGIO 的信號),然後應用用戶進程可以去做別的事,不用阻塞。當內核數據準備好後,再通過 SIGIO 信號通知應用進程,數據準備好後的可讀狀態。應用用戶進程收到信號之後,立即調用 recvfrom,去讀取數據。
4.5 IO 模型之異步IO(AIO)AIO 實現了 IO 全流程的非阻塞,就是應用進程發出系統調用後,是立即返回的,但是立即返回的不是處理結果,而是表示提交成功類似的意思。等內核數據準備好,將數據拷貝到用戶進程緩衝區,發送信號通知用戶進程 IO 操作執行完畢。
流程如下:
5. Hystrix 工作原理Hystrix 工作流程圖如下:

Hystrix 提供了兩個命令對象:HystrixCommand和HystrixObservableCommand,它將代表你的一個依賴請求任務,向構造函數中傳入請求依賴所需要的參數。
有四種方式執行 Hystrix 命令。分別是:
R execute():同步阻塞執行的,從依賴請求中接收到單個響應。Future queue():異步執行,返回一個包含單個響應的Future對象。Observable observe():創建Observable後會訂閱Observable,從依賴請求中返回代表響應的Observable對象Observable toObservable():cold observable,返回一個Observable,只有訂閱時才會執行Hystrix命令,可以返回多個結果
如果啟用了 Hystrix 緩存,任務執行前將先判斷是否有相同命令執行的緩存。如果有則直接返回包含緩存響應的 Observable;如果沒有緩存的結果,但啟動了緩存,將緩存本次執行結果以供後續使用。
迴路器(circuit-breaker)和保險絲類似,保險絲在發生危險時將會燒斷以保護電路,而迴路器可以在達到我們設定的閥值時觸發短路(比如請求失敗率達到 50%),拒絕執行任何請求。
如果迴路器被打開,Hystrix 將不會執行命令,直接進入 Fallback 處理邏輯。
5)檢查線程池 / 信號量 / 隊列情況
Hystrix 隔離方式有線程池隔離和信號量隔離。當使用 Hystrix 線程池時,Hystrix 默認為每個依賴服務分配 10 個線程,當 10 個線程都繁忙時,將拒絕執行命令,而是立即跳到執行 fallback 邏輯。
6) 執行具體的任務
通過 HystrixObservableCommand.construct() 或者 HystrixCommand.run() 來運行用戶真正的任務。
7) 計算迴路健康情況
每次開始執行 command、結束執行 command 以及發生異常等情況時,都會記錄執行情況,例如:成功、失敗、拒絕和超時等指標情況,會定期處理這些數據,再根據設定的條件來判斷是否開啟迴路器。
8) 命令失敗時執行 Fallback 邏輯
在命令失敗時執行用戶指定的 Fallback 邏輯。上圖中的斷路、線程池拒絕、信號量拒絕、執行執行、執行超時都會進入 Fallback 處理。
9) 返回執行結果
原始對象結果將以 Observable 形式返回,在返回給用戶之前,會根據調用方式的不同做一些處理。
6. 延時場景處理日常開發中,我們經常遇到這種業務場景,如外賣訂單超 30 分鐘未支付,則自動取消訂單;用戶註冊成功 15 分鐘後,發短信消息通知用戶等等。這就是延時任務處理場景。
針對此類場景我們主要有以下幾種處理方案:
7. https 請求過程HTTPS = HTTP + SSL/TLS,即用 SSL/TLS 對數據進行加密和解密,Http 進行傳輸。SSL,即 Secure Sockets Layer(安全套接層協議),是網絡通信提供安全及數據完整性的一種安全協議。TLS,即 Transport Layer Security(安全傳輸層協議),它是 SSL 3.0 的後續版本。
http請求流程用戶在瀏覽器里輸入一個 https 網址,然後連接到 server 的 443 端口。服務器必須要有一套數字證書,可以自己製作,也可以向組織申請,區別就是自己頒發的證書需要客戶端驗證通過。這套證書其實就是一對公鑰和私鑰。客戶端收到服務器端的數字證書之後,會對其進行檢查,如果不通過,則彈出警告框。如果證書沒問題,則生成一個密鑰(對稱加密),用證書的公鑰對它加密。客戶端會發起 HTTPS 中的第二個 HTTP 請求,將加密之後的客戶端密鑰發送給服務器。服務器接收到客戶端發來的密文之後,會用自己的私鑰對其進行非對稱解密,解密之後得到客戶端密鑰,然後用客戶端密鑰對返回數據進行對稱加密,這樣數據就變成了密文。客戶端收到服務器發返回的密文,用自己的密鑰(客戶端密鑰)對其進行對稱解密,得到服務器返回的數據。8. 聊聊事務隔離級別,以及可重複讀實現原理8.1 數據庫四大隔離級別為了解決並發事務存在的髒讀、不可重複讀、幻讀等問題,數據庫大叔設計了四種隔離級別。分別是讀未提交,讀已提交,可重複讀,串行化(Serializable)。
讀未提交隔離級別:只限制了兩個數據不能同時修改,但是修改數據的時候,即使事務未提交,都是可以被別的事務讀取到的,這級別的事務隔離有髒讀、重複讀、幻讀的問題;讀已提交隔離級別:當前事務只能讀取到其他事務提交的數據,所以這種事務的隔離級別解決了髒讀問題,但還是會存在重複讀、幻讀問題;可重複讀:限制了讀取數據的時候,不可以進行修改,所以解決了重複讀的問題,但是讀取範圍數據的時候,是可以插入數據,所以還會存在幻讀問題;串行化:事務最高的隔離級別,在該級別下,所有事務都是進行串行化順序執行的。可以避免髒讀、不可重複讀與幻讀所有並發問題。但是這種事務隔離級別下,事務執行很耗性能。四大隔離級別,都會存在哪些並發問題呢?

8.2 Read View 可見性規則
Read View 的可見性規則如下:
如果數據事務 ID trx_id < min_limit_id,表明生成該版本的事務在生成 Read View前,已經提交(因為事務 ID 是遞增的),所以該版本可以被當前事務訪問。如果 trx_id>= max_limit_id,表明生成該版本的事務在生成 Read View 後才生成,所以該版本不可以被當前事務訪問。如果min_limit_id =<trx_id< max_limit_id,需要分3種情況討論如果 m_ids 包含 trx_id,則代表 Read View 生成時刻,這個事務還未提交。但是如果數據的 trx_id 等於 creator_trx_id 的話,表明數據是自己生成的,因此是可見的。如果 m_ids 包含 trx_id,並且 trx_id 不等於 creator_trx_id,則 Read View 生成時,事務未提交,並且不是自己生產的,所以當前事務也是看不見的;如果 m_ids 不包含 trx_id,則說明你這個事務在 Read View 生成之前就已經提交了,修改的結果,當前事務是能看見的。8.3 可重複讀實現原理數據庫是通過加鎖實現隔離級別的,比如,你想一個人靜靜,不被別人打擾,你可以把自己關在房子,並在房門上加上一把鎖!串行化隔離級別就是加鎖實現的。但是如果頻繁加鎖,性能會下降。因此設計數據庫的大叔想到了 MVCC。
可重複讀的實現原理就是 MVCC 多版本並發控制。在一個事務範圍內,兩個相同的查詢,讀取同一條記錄,卻返回了不同的數據,這就是不可重複讀。可重複讀隔離級別,就是為了解決不可重複讀問題。
查詢一條記錄,基於 MVCC,是怎樣的流程呢?
查詢得到的數據,然後 Read View 中的事務版本號進行比較。如果不符合 Read View 的可見性規則, 即就需要 Undo log 中歷史快照;InnoDB 實現 MVCC,是通過 Read View + Undo Log 實現的,Undo Log 保存了歷史快照,Read View 可見性規則幫助判斷當前版本的數據是否可見。
可重複讀(RR)隔離級別,是如何解決不可重複讀問題的?
假設存在事務 A 和 B,SQL 執行流程如下:

在可重複讀(RR)隔離級別下,一個事務里只會獲取一次 read view,都是副本共用的,從而保證每次查詢的數據都是一樣的。
假設當前有一張 core_user 表,插入一條初始化數據,如下:

基於 MVCC,我們來看看執行流程:
1. A 開啟事務,首先得到一個事務 ID 為 1003. 事務 A 生成一個 Read View,read view對應的值如下:
然後回到版本鏈,開始從版本鏈中挑選可見的記錄:

由圖可以看出,最新版本的列 name 的內容是孫權,該版本的 trx_id 值為 100。開始執行 read view 可見性規則校驗:
min_limit_id(100)=<trx_id(100)<102;creator_trx_id = trx_id =100;由此可得,trx_id=100 的這個記錄,當前事務是可見的。所以查到是 name 為孫權的記錄。
把原數據拷貝到 undo log,然後對數據進行修改。標記事務 ID 和上一個數據版本在 undo log 的地址。

5. 事務 B 提交事務
6. 事務 A 再次執行查詢操作,因為是 RR(可重複讀)隔離級別,因此會復用老的 Read View 副本,Read View 對應的值如下:
然後再次回到版本鏈,從版本鏈中挑選可見的記錄:

從圖可得,最新版本的列 name 的內容是曹操,該版本的 trx_id 值為 101。開始執行 read view 可見性規則校驗:
min_limit_id(100)=<trx_id(101)<max_limit_id(102);因為 m_ids{100,101} 包含 trx_id(101),並且 creator_trx_id(100)不等於 trx_id(101),所以 trx_id=101 這個記錄,對於當前事務是不可見的。這時候呢,版本鏈 roll_pointer 跳到下一個版本,trx_id=100 這個記錄,再次校驗是否可見:min_limit_id(100)=<trx_id(100)< max_limit_id(102);因為 m_ids{100,101} 包含 trx_id(100),並且 creator_trx_id(100) 等於 trx_id(100),所以,trx_id=100 這個記錄,對於當前事務是可見的,所以兩次查詢結果,都是 name=孫權 的那個記錄。即在可重複讀(RR)隔離級別下,復用老的 Read View 副本,解決了不可重複讀的問題。9. 聊聊索引在哪些場景下會失效?查詢條件包含 or,可能導致索引失效;如何字段類型是字符串,where 時一定用引號括起來,否則索引失效;like 通配符可能導致索引失效;聯合索引,查詢時的條件列不是聯合索引中的第一個列,索引失效;在索引列上使用 mysql 的內置函數,索引失效;對索引列運算(如 +、-、*、/),索引失效;索引字段上使用(!= 或者 < >,not in)時,可能會導致索引失效;索引字段上使用 is null, is not null 可能導致索引失效;左連接查詢或者右連接查詢查詢關聯的字段編碼格式不一樣,可能導致索引失效。MySQL 估計使用全表掃描要比使用索引快,則不使用索引。10. 什麼是虛擬內存虛擬內存,是虛擬出來的內存,它的核心思想就是確保每個程序擁有自己的地址空間,地址空間被分成多個塊,每一塊都有連續的地址空間。同時物理空間也分成多個塊,塊大小和虛擬地址空間的塊大小一致,操作系統會自動將虛擬地址空間映射到物理地址空間,程序只需關注虛擬內存,請求的也是虛擬內存,真正使用卻是物理內存。
現代操作系統使用虛擬內存,即虛擬地址取代物理地址,使用虛擬內存可以有 2 個好處:
零拷貝實現思想,就利用了虛擬內存這個點:多個虛擬內存可以指向同一個物理地址,可以把內核空間和用戶空間的虛擬地址映射到同一個物理地址,這樣的話,就可以減少 IO 的數據拷貝次數啦,示意圖如下:
11. 排行榜的實現,比如高考成績排序排行版的實現,一般使用 Redis 的 zset 數據類型。
zadd key score member [score member ...],zrank key member
層內部編碼:ziplist(壓縮列表)、skiplist(跳躍表)實現 demo 如下:
12.分布式鎖實現分布式鎖,是控制分布式系統不同進程共同訪問共享資源的一種鎖的實現。秒殺下單、搶紅包等等業務場景,都需要用到分布式鎖。
我們項目中經常使用 Redis 作為分布式鎖。選了 Redis 分布式鎖的幾種實現方法,大家來討論下,看有沒有啥問題哈。
set ex px nx + 校驗唯一隨機值,再刪除12.1 命令 setnx + expire 分開寫if(jedis.setnx(key,lock_value) == 1){ //加鎖 expire(key,100); //設置過期時間 try { do something //業務請求 }catch(){ } finally { jedis.del(key); //釋放鎖 }}如果執行完 setnx 加鎖,正要執行 expire 設置過期時間時,進程 crash 掉或者要重啟維護了,那這個鎖就「長生不老」了,別的線程永遠獲取不到鎖啦,所以分布式鎖不能這麼實現。
12.2 setnx + value 值是過期時間long expires = System.currentTimeMillis() + expireTime; //系統時間+設置的過期時間String expiresStr = String.valueOf(expires);// 如果當前鎖不存在,返回加鎖成功if (jedis.setnx(key, expiresStr) == 1) { return true;} // 如果鎖已經存在,獲取鎖的過期時間String currentValueStr = jedis.get(key);// 如果獲取到的過期時間,小於系統當前時間,表示已經過期if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { // 鎖已過期,獲取上一個鎖的過期時間,並設置現在鎖的過期時間(不了解redis的getSet命令的小夥伴,可以去官網看下哈) String oldValueStr = jedis.getSet(key_resource_id, expiresStr); if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { // 考慮多線程並發的情況,只有一個線程的設置值和當前值相同,它才可以加鎖 return true; }} //其他情況,均返回加鎖失敗return false;}筆者看過有開發小夥伴就是這麼實現分布式鎖的,但是這種方案也有這些缺點:
過期時間是客戶端自己生成的,分布式環境下,每個客戶端的時間必須同步。沒有保存持有者的唯一標識,可能被別的客戶端釋放 / 解鎖。鎖過期的時候,並發多個客戶端同時請求過來,都執行了 jedis.getSet(),最終只能有一個客戶端加鎖成功,但是該客戶端鎖的過期時間,可能被別的客戶端覆蓋。12.3 set 的擴展命令(set ex px nx)(注意可能存在的問題)if(jedis.set(key, lock_value, "NX", "EX", 100s) == 1){ //加鎖 try { do something //業務處理 }catch(){ } finally { jedis.del(key); //釋放鎖 }}這個方案可能存在這樣的問題:
12.4 set ex px nx + 校驗唯一隨機值,再刪除if(jedis.set(key, uni_request_id, "NX", "EX", 100s) == 1){ //加鎖 try { do something //業務處理 }catch(){ } finally { //判斷是不是當前線程加的鎖,是才釋放 if (uni_request_id.equals(jedis.get(key))) { jedis.del(key); //釋放鎖 } }}在這裡,判斷當前線程加的鎖和釋放鎖是不是一個原子操作。如果調用 jedis.del() 釋放鎖的時候,可能這把鎖已經不屬於當前客戶端,會解除他人加的鎖。
一般也是用 lua 腳本代替。lua 腳本如下:
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0end;這種方式比較不錯了,一般情況下,已經可以使用這種實現方式。但是存在鎖過期釋放了,業務還沒執行完的問題(實際上,估算個業務處理的時間,一般沒啥問題了)。
12.5 Redisson分布式鎖可能存在鎖過期釋放,業務沒執行完的問題。有些小夥伴認為,稍微把鎖過期時間設置長一些就可以啦。其實我們設想一下,是否可以給獲得鎖的線程,開啟一個定時守護線程,每隔一段時間檢查鎖是否還存在,存在則對鎖的過期時間延長,防止鎖過期提前釋放。
當前開源框架 Redisson 就解決了這個分布式鎖問題。我們一起來看下 Redisson 底層原理是怎樣的吧:

只要線程一加鎖成功,就會啟動一個 watch dog 看門狗,它是一個後台線程,會每隔 10 秒檢查一下,如果線程 1 還持有鎖,那麼就會不斷的延長鎖 key 的生存時間。因此,Redisson 就是使用 Redisson 解決了鎖過期釋放,業務沒執行完問題。
13. 零拷貝零拷貝就是不需要將數據從一個存儲區域複製到另一個存儲區域。它是指在傳統 IO 模型中,指 CPU 拷貝的次數為 0。它是 IO 的優化方案:
零拷貝實現之帶有 DMA 收集拷貝功能的 sendfile13.1 傳統 IO 流程流程圖如下:
用戶應用進程調用 read 函數,向操作系統發起 IO 調用,上下文從用戶態轉為內核態(切換 1)CPU 把內核緩衝區數據,拷貝到用戶應用緩衝區,上下文從內核態轉為用戶態(切換 2),read 函數返回用戶應用進程通過 write 函數,發起 IO 調用,上下文從用戶態轉為內核態(切換3)CPU 將應用緩衝區中的數據,拷貝到 socket 緩衝區DMA 控制器把數據從 socket 緩衝區,拷貝到網卡設備,上下文從內核態切換回用戶態(切換 4),write 函數返回從流程圖可以看出,傳統 IO 的讀寫流程,包括了 4 次上下文切換(4 次用戶態和內核態的切換),4 次數據拷貝(兩次 CPU 拷貝以及兩次的 DMA 拷貝)。
13.2 mmap+write 實現的零拷貝mmap 的函數原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);mmap 使用了虛擬內存,可以把內核空間和用戶空間的虛擬地址映射到同一個物理地址,從而減少數據拷貝次數!
mmap+write 實現的零拷貝流程如下:
用戶進程通過 mmap 方法向操作系統內核發起 IO 調用,上下文從用戶態切換為內核態。CPU 利用 DMA 控制器,把數據從硬盤中拷貝到內核緩衝區。用戶進程通過 write 方法向操作系統內核發起 IO 調用,上下文從用戶態切換為內核態。CPU 將內核緩衝區的數據拷貝到的 socket 緩衝區。CPU 利用 DMA 控制器,把數據從 socket 緩衝區拷貝到網卡,上下文從內核態切換回用戶態,write 調用返回。可以發現,mmap+write 實現的零拷貝,I/O 發生了 4 次用戶空間與內核空間的上下文切換,以及 3 次數據拷貝。其中3次數據拷貝中,包括了 2 次 DMA 拷貝和 1 次 CPU 拷貝。
mmap 是將讀緩衝區的地址和用戶緩衝區的地址進行映射,內核緩衝區和應用緩衝區共享,所以節省了一次 CPU 拷貝『』並且用戶進程內存是虛擬的,只是映射到內核的讀緩衝區,可以節省一半的內存空間。
sendfile 實現的零拷貝sendfile 是 Linux2.1 內核版本後引入的一個系統調用函數,API 如下:
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);out_fd:為待寫入內容的文件描述符,一個socket描述符。in_fd:為待讀出內容的文件描述符,必須是真實的文件,不能是 socket 和管道。offset:指定從讀入文件的哪個位置開始讀,如果為 NULL,表示文件的默認起始位置。count:指定在 fdout 和 fdin 之間傳輸的字節數。sendfile 表示在兩個文件描述符之間傳輸數據,它是在操作系統內核中操作的,避免了數據從內核緩衝區和用戶緩衝區之間的拷貝操作,因此可以使用它來實現零拷貝。
sendfile 實現的零拷貝流程如下:
sendfile 實現的零拷貝用戶進程發起 sendfile 系統調用,上下文(切換 1)從用戶態轉向內核態CPU 將讀緩衝區中數據拷貝到 socket 緩衝區DMA 控制器,異步把數據從 socket 緩衝區拷貝到網卡,上下文(切換 2)從內核態切換回用戶態,sendfile 調用返回。可以發現,sendfile 實現的零拷貝,I/O 發生了 2 次用戶空間與內核空間的上下文切換,以及 3 次數據拷貝。其中 3 次數據拷貝中,包括了 2次 DMA 拷貝和 1 次 CPU 拷貝。
那能不能把 CPU 拷貝的次數減少到 0 次呢?
有的,即帶有 DMA 收集拷貝功能的 sendfile!
sendfile+DMA scatter/gather 實現的零拷貝Linux 2.4 版本之後,對 sendfile 做了優化升級,引入 SG-DMA 技術,其實就是對 DMA 拷貝加入了 scatter/gather 操作,它可以直接從內核空間緩衝區中將數據讀取到網卡。使用這個特點搞零拷貝,即還可以多省去一次 CPU 拷貝。
sendfile+DMA scatter/gather 實現的零拷貝流程如下:
用戶進程發起 sendfile 系統調用,上下文(切換 1)從用戶態轉向內核態。CPU 把內核緩衝區中的文件描述符信息(包括內核緩衝區的內存地址和偏移量)發送到 socket 緩衝區。DMA 控制器根據文件描述符信息,直接把數據從內核緩衝區拷貝到網卡。上下文(切換 2)從內核態切換回用戶態,sendfile 調用返回。可以發現,sendfile+DMA scatter/gather 實現的零拷貝,I/O 發生了 2 次用戶空間與內核空間的上下文切換,以及 2 次數據拷貝。其中 2 次數據拷貝都是包 DMA 拷貝。這就是真正的零拷貝(Zero-copy)技術,全程都沒有通過CPU來搬運數據,所有的數據都是通過 DMA 來進行傳輸的。
14. synchronizedsynchronized 是 Java 中的關鍵字,是一種同步鎖。synchronized 關鍵字可以作用於方法或者代碼塊。
一般面試時。可以這麼回答:
反編譯後,monitorenter、monitorexit、ACC_SYNCHRONIZED14.1 monitorenter、monitorexit、ACC_SYNCHRONIZED如果 synchronized 作用於代碼塊,反編譯可以看到兩個指令:monitorenter、monitorexit。JVM 使用 monitorenter 和 monitorexit 兩個指令實現同步。
如果作用 synchronized 作用於方法,反編譯可以看到 ACCSYNCHRONIZED 標記。JVM 通過在方法訪問標識符(flags)中加入 ACCSYNCHRONIZED 來實現同步功能。
同步代碼塊是通過 monitorenter 和 monitorexit 來實現。當線程執行到 monitorenter 的時候要先獲得 monitor 鎖,才能執行後面的方法。當線程執行到 monitorexit 的時候則要釋放鎖。同步方法是通過中設置 ACCSYNCHRONIZED 標誌來實現,當線程執行有 ACCSYNCHRONIZED 標誌的方法,需要獲得 monitor 鎖。每個對象都與一個 monitor 相關聯,線程可以占有或者釋放 monitor。14.2 monitor 監視器monitor 是什麼呢?操作系統的管程(monitors)是概念原理,ObjectMonitor 是它的原理實現。

在 Java 虛擬機(HotSpot)中,Monitor(管程)是由 ObjectMonitor 實現的,其主要數據結構如下:
ObjectMonitor() { _header = NULL; _count = 0; // 記錄個數 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; // 處於wait狀態的線程,會被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; // 處於等待鎖block狀態的線程,會被加入到該列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ;}ObjectMonitor 中幾個關鍵字段的含義如圖所示:
14.3 Java Monitor 的工作機理
想要獲取monitor的線程,首先會進入 _EntryList 隊列。當某個線程獲取到對象的 monitor 後,進入Owner 區域,設置為當前線程,同時計數器 count 加 1。如果線程調用了 wait() 方法,則會進入 WaitSet 隊列。它會釋放 monitor 鎖,即將 owne r賦值為 null,count 自減 1,進入 WaitSet 隊列阻塞等待。如果其他線程調用 notify() / notifyAll() ,會喚醒 WaitSet 中的某個線程,該線程再次嘗試獲取 monitor 鎖,成功即進入 Owner 區域。同步方法執行完畢了,線程退出臨界區,會將 monitor 的 owner 設為 null,並釋放監視鎖。14.4 對象與 monitor 關聯
在 HotSpot 虛擬機中,對象在內存中存儲的布局可以分為 3 塊區域:對象頭(Header),實例數據(Instance Data)和對象填充(Padding)。對象頭主要包括兩部分數據:Mark Word(標記字段)、Class Pointer(類型指針)。Mark Word 是用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程 ID、偏向時間戳等。

重量級鎖,指向互斥量的指針。其實 synchronized 是重量級鎖,也就是說 Synchronized 的對象鎖,Mark Word 鎖標識位為 10,其中指針指向的是 Monitor 對象的起始地址。
15. 分布式 id 生成方案有哪些?什麼是雪花算法?分布式 id 生成方案主要有:
什麼是雪花算法?
雪花算法是一種生成分布式全局唯一 ID 的算法,生成的 ID 稱為 Snowflake IDs。這種算法由 Twitter 創建,並用於推文的 ID。
一個 Snowflake ID 有 64 位。
第 1 位:Java 中 long 的最高位是符號位代表正負,正數是 0,負數是 1。一般生成 ID 都為正數,所以默認為 0。接下來前 41 位是時間戳,表示了自選定的時期以來的毫秒數。其餘 12 位代表每台機器上生成 ID 的序列號,這允許在同一毫秒內創建多個Snowflake ID。
雪花算法