落淚!滴滴一面,涼了......
大家好,我是小林。
今天就來分享 Java 同學(xué)面試滴滴后端開發(fā)的面經(jīng),主要是問了Java+MySQL+系統(tǒng)+網(wǎng)絡(luò)+算法,都是比較經(jīng)典面試題,不算難。
圖片
可惜最后同學(xué)還是掛了,掛了沒關(guān)系,重在復(fù)盤每一次的面經(jīng),針對面試中不理解或者不明白的問題看書繼續(xù)加強(qiáng)理解,針對已經(jīng)知道,但是講不清楚的問題,自己對著鏡子面前練習(xí)。
考察的知識內(nèi)容,我?guī)痛蠹伊_列了一下:
- 操作系統(tǒng):進(jìn)程線程協(xié)程、進(jìn)程狀態(tài)、io 模型、io 多路復(fù)用
- 計(jì)算機(jī)網(wǎng)絡(luò):http 請求頭、狀態(tài)碼
- Java:集合、JVM
- mysql:事務(wù)、并發(fā)問題
- 算法:二分
操作系統(tǒng)
進(jìn)程,線程,協(xié)程的區(qū)別是什么?
- 首先,我們來談?wù)勥M(jìn)程。進(jìn)程是操作系統(tǒng)中進(jìn)行資源分配和調(diào)度的基本單位,它擁有自己的獨(dú)立內(nèi)存空間和系統(tǒng)資源。每個進(jìn)程都有獨(dú)立的堆和棧,不與其他進(jìn)程共享。進(jìn)程間通信需要通過特定的機(jī)制,如管道、消息隊(duì)列、信號量等。由于進(jìn)程擁有獨(dú)立的內(nèi)存空間,因此其穩(wěn)定性和安全性相對較高,但同時上下文切換的開銷也較大,因?yàn)樾枰4婧突謴?fù)整個進(jìn)程的狀態(tài)。
- 接下來是線程。線程是進(jìn)程內(nèi)的一個執(zhí)行單元,也是CPU調(diào)度和分派的基本單位。與進(jìn)程不同,線程共享進(jìn)程的內(nèi)存空間,包括堆和全局變量。線程之間通信更加高效,因?yàn)樗鼈兛梢灾苯幼x寫共享內(nèi)存。線程的上下文切換開銷較小,因?yàn)橹恍枰4婧突謴?fù)線程的上下文,而不是整個進(jìn)程的狀態(tài)。然而,由于多個線程共享內(nèi)存空間,因此存在數(shù)據(jù)競爭和線程安全的問題,需要通過同步和互斥機(jī)制來解決。
- 最后是協(xié)程。協(xié)程是一種用戶態(tài)的輕量級線程,其調(diào)度完全由用戶程序控制,而不需要內(nèi)核的參與。協(xié)程擁有自己的寄存器上下文和棧,但與其他協(xié)程共享堆內(nèi)存。協(xié)程的切換開銷非常小,因?yàn)橹恍枰4婧突謴?fù)協(xié)程的上下文,而無需進(jìn)行內(nèi)核級的上下文切換。這使得協(xié)程在處理大量并發(fā)任務(wù)時具有非常高的效率。然而,協(xié)程需要程序員顯式地進(jìn)行調(diào)度和管理,相對于線程和進(jìn)程來說,其編程模型更為復(fù)雜。
進(jìn)程的狀態(tài)(五種狀態(tài)),如何切換?
一個完整的進(jìn)程狀態(tài)的變遷如下圖:
圖片
進(jìn)程五種狀態(tài)的變遷 再來詳細(xì)說明一下進(jìn)程的狀態(tài)變遷:
- _NULL -> 創(chuàng)建狀態(tài)_:一個新進(jìn)程被創(chuàng)建時的第一個狀態(tài);
- _創(chuàng)建狀態(tài) -> 就緒狀態(tài)_:當(dāng)進(jìn)程被創(chuàng)建完成并初始化后,一切就緒準(zhǔn)備運(yùn)行時,變?yōu)榫途w狀態(tài),這個過程是很快的;
- _就緒態(tài) -> 運(yùn)行狀態(tài)_:處于就緒狀態(tài)的進(jìn)程被操作系統(tǒng)的進(jìn)程調(diào)度器選中后,就分配給 CPU 正式運(yùn)行該進(jìn)程;
- _運(yùn)行狀態(tài) -> 結(jié)束狀態(tài)_:當(dāng)進(jìn)程已經(jīng)運(yùn)行完成或出錯時,會被操作系統(tǒng)作結(jié)束狀態(tài)處理;
- _運(yùn)行狀態(tài) -> 就緒狀態(tài)_:處于運(yùn)行狀態(tài)的進(jìn)程在運(yùn)行過程中,由于分配給它的運(yùn)行時間片用完,操作系統(tǒng)會把該進(jìn)程變?yōu)榫途w態(tài),接著從就緒態(tài)選中另外一個進(jìn)程運(yùn)行;
- _運(yùn)行狀態(tài) -> 阻塞狀態(tài)_:當(dāng)進(jìn)程請求某個事件且必須等待時,例如請求 I/O 事件;
- _阻塞狀態(tài) -> 就緒狀態(tài)_:當(dāng)進(jìn)程要等待的事件完成時,它從阻塞狀態(tài)變到就緒狀態(tài);
你了解過哪些io模型?
- 阻塞I/O模型:應(yīng)用程序發(fā)起I/O操作后會被阻塞,直到操作完成才返回結(jié)果。適用于對實(shí)時性要求不高的場景。
- 非阻塞I/O模型:應(yīng)用程序發(fā)起I/O操作后立即返回,不會被阻塞,但需要不斷輪詢或者使用select/poll/epoll等系統(tǒng)調(diào)用來檢查I/O操作是否完成。適合于需要進(jìn)行多路復(fù)用的場景,例如需要同時處理多個socket連接的服務(wù)器程序。
- I/O復(fù)用模型:通過select、poll、epoll等系統(tǒng)調(diào)用,應(yīng)用程序可以同時等待多個I/O操作,當(dāng)其中任何一個I/O操作準(zhǔn)備就緒時,應(yīng)用程序會被通知。適合于需要同時處理多個I/O操作的場景,比如高并發(fā)的服務(wù)端程序。
- 信號驅(qū)動I/O模型:應(yīng)用程序發(fā)起I/O操作后,可以繼續(xù)做其他事情,當(dāng)I/O操作完成時,操作系統(tǒng)會向應(yīng)用程序發(fā)送信號來通知其完成。適合于需要異步I/O通知的場景,可以提高系統(tǒng)的并發(fā)能力。
- 異步I/O模型:應(yīng)用程序發(fā)起I/O操作后可以立即做其他事情,當(dāng)I/O操作完成時,應(yīng)用程序會得到通知。異步I/O模型由操作系統(tǒng)內(nèi)核完成I/O操作,應(yīng)用程序只需等待通知即可。適合于需要大量并發(fā)連接和高性能的場景,能夠減少系統(tǒng)調(diào)用次數(shù),提高系統(tǒng)效率。
有了解過io多路復(fù)用嗎?
IO多路復(fù)用是一種高效的IO處理方式,它允許單個進(jìn)程或線程同時監(jiān)視多個文件描述符,如網(wǎng)絡(luò)連接或文件句柄。當(dāng)這些描述符中的任何一個就緒時,比如有數(shù)據(jù)可讀或可寫,多路復(fù)用機(jī)制就能夠通知應(yīng)用程序進(jìn)行相應(yīng)的讀寫操作。這種機(jī)制的核心優(yōu)勢在于,它可以在不增加額外線程或進(jìn)程的情況下,處理大量的并發(fā)連接,從而顯著地提高系統(tǒng)的并發(fā)性和響應(yīng)能力。常見的IO多路復(fù)用技術(shù)包括select、poll和epoll等。這些技術(shù)各有特點(diǎn),但核心思想都是通過一個線程來管理多個連接,減少系統(tǒng)資源的消耗,并提高程序運(yùn)行的效率。select 實(shí)現(xiàn)多路復(fù)用的方式是,將已連接的 Socket 都放到一個文件描述符集合,然后調(diào)用 select 函數(shù)將文件描述符集合拷貝到內(nèi)核里,讓內(nèi)核來檢查是否有網(wǎng)絡(luò)事件產(chǎn)生,檢查的方式很粗暴,就是通過遍歷文件描述符集合的方式,當(dāng)檢查到有事件產(chǎn)生后,將此 Socket 標(biāo)記為可讀或可寫, 接著再把整個文件描述符集合拷貝回用戶態(tài)里,然后用戶態(tài)還需要再通過遍歷的方法找到可讀或可寫的 Socket,然后再對其處理。所以,對于 select 這種方式,需要進(jìn)行 2 次「遍歷」文件描述符集合,一次是在內(nèi)核態(tài)里,一個次是在用戶態(tài)里 ,而且還會發(fā)生 2 次「拷貝」文件描述符集合,先從用戶空間傳入內(nèi)核空間,由內(nèi)核修改后,再傳出到用戶空間中。select 使用固定長度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的個數(shù)是有限制的,在 Linux 系統(tǒng)中,由內(nèi)核中的 FD_SETSIZE 限制, 默認(rèn)最大值為 1024,只能監(jiān)聽 0~1023 的文件描述符。poll 不再用 BitsMap 來存儲所關(guān)注的文件描述符,取而代之用動態(tài)數(shù)組,以鏈表形式來組織,突破了 select 的文件描述符個數(shù)限制,當(dāng)然還會受到系統(tǒng)文件描述符限制。但是 poll 和 select 并沒有太大的本質(zhì)區(qū)別,都是使用「線性結(jié)構(gòu)」存儲進(jìn)程關(guān)注的 Socket 集合,因此都需要遍歷文件描述符集合來找到可讀或可寫的 Socket,時間復(fù)雜度為 O(n),而且也需要在用戶態(tài)與內(nèi)核態(tài)之間拷貝文件描述符集合,這種方式隨著并發(fā)數(shù)上來,性能的損耗會呈指數(shù)級增長。poll 通過兩個方面,很好解決了 select/poll 的問題。
- _第一點(diǎn)_,epoll 在內(nèi)核里使用紅黑樹來跟蹤進(jìn)程所有待檢測的文件描述字,把需要監(jiān)控的 socket 通過 epoll_ctl() 函數(shù)加入內(nèi)核中的紅黑樹里,紅黑樹是個高效的數(shù)據(jù)結(jié)構(gòu),增刪改一般時間復(fù)雜度是 O(logn)。而 select/poll 內(nèi)核里沒有類似 epoll 紅黑樹這種保存所有待檢測的 socket 的數(shù)據(jù)結(jié)構(gòu),所以 select/poll 每次操作時都傳入整個 socket 集合給內(nèi)核,而 epoll 因?yàn)樵趦?nèi)核維護(hù)了紅黑樹,可以保存所有待檢測的 socket ,所以只需要傳入一個待檢測的 socket,減少了內(nèi)核和用戶空間大量的數(shù)據(jù)拷貝和內(nèi)存分配。
- _第二點(diǎn)_, epoll 使用事件驅(qū)動的機(jī)制,內(nèi)核里維護(hù)了一個鏈表來記錄就緒事件,當(dāng)某個 socket 有事件發(fā)生時,通過回調(diào)函數(shù)內(nèi)核會將其加入到這個就緒事件列表中,當(dāng)用戶調(diào)用 epoll_wait() 函數(shù)時,只會返回有事件發(fā)生的文件描述符的個數(shù),不需要像 select/poll 那樣輪詢掃描整個 socket 集合,大大提高了檢測的效率。
從下圖你可以看到 epoll 相關(guān)的接口作用:
圖片
epoll 的方式即使監(jiān)聽的 Socket 數(shù)量越多的時候,效率不會大幅度降低,能夠同時監(jiān)聽的 Socket 的數(shù)目也非常的多了,上限就為系統(tǒng)定義的進(jìn)程打開的最大文件描述符個數(shù)。因而,epoll 被稱為解決 C10K 問題的利器。
計(jì)算機(jī)網(wǎng)絡(luò)
Http請求頭有哪些
下面是一個HTTP請求的請求頭:
GET /home.html HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/testpage.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
If-Modified-Since: Mon, 18 Jul 2016 02:36:04 GMT
If-None-Match: "c561c68d0ba92bbeb8b0fff2a9199f722e3a621a"
Cache-Control: max-age=0
常見的請求字段如下表所示:
字段名 | 說明 | 示例 |
Accept | 能夠接受的回應(yīng)內(nèi)容類型(Content-Types) | Accept: text/plain |
Accept-Charset | 能夠接受的字符集 | Accept-Charset: utf-8 |
Accept-Encoding | 能夠接受的編碼方式列表 | Accept-Encoding: gzip, deflate |
Authorization | 用于超文本傳輸協(xié)議的認(rèn)證的認(rèn)證信息 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Cache-Control | 用來指定在這次的請求/響應(yīng)鏈中的所有緩存機(jī)制 都必須 遵守的指令 | Cache-Control: no-cache |
Connection | 該瀏覽器想要優(yōu)先使用的連接類型 | Connection: keep-alive Connection: Upgrade |
Cookie | 服務(wù)器通過 Set- Cookie (下文詳述)發(fā)送的一個 超文本傳輸協(xié)議Cookie | Cookie: $Versinotallow=1; Skin=new; |
Content-Length | 以 八位字節(jié)數(shù)組 (8位的字節(jié))表示的請求體的長度 | Content-Length: 348 |
Content-Type | 請求體的 多媒體類型 | Content-Type: application/x-www-form-urlencoded |
Host | 服務(wù)器的域名(用于虛擬主機(jī) ),以及服務(wù)器所監(jiān)聽的傳輸控制協(xié)議端口號 | Host: en.wikipedia.org:80 Host: en.wikipedia.org |
User-Agent | 瀏覽器的瀏覽器身份標(biāo)識字符串 | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0 |
Origin | 發(fā)起一個針對 跨來源資源共享 的請求 | Origin: http://www.example-social-network.com |
常見的狀態(tài)碼(分類舉例說明)
圖片
五大類 HTTP 狀態(tài)碼
- 1xx 類狀態(tài)碼屬于提示信息,是協(xié)議處理中的一種中間狀態(tài),實(shí)際用到的比較少。
- 2xx 類狀態(tài)碼表示服務(wù)器成功處理了客戶端的請求,也是我們最愿意看到的狀態(tài)。
- 3xx 類狀態(tài)碼表示客戶端請求的資源發(fā)生了變動,需要客戶端用新的 URL 重新發(fā)送請求獲取資源,也就是重定向。
- 4xx 類狀態(tài)碼表示客戶端發(fā)送的報(bào)文有誤,服務(wù)器無法處理,也就是錯誤碼的含義。
- 5xx 類狀態(tài)碼表示客戶端請求報(bào)文正確,但是服務(wù)器處理時內(nèi)部發(fā)生了錯誤,屬于服務(wù)器端的錯誤碼。
301和302有什么區(qū)別
重定向狀態(tài)碼如下,301 和 302 都會在響應(yīng)頭里使用字段 Location,指明后續(xù)要跳轉(zhuǎn)的 URL,瀏覽器會自動重定向新的 URL。
- 「301 Moved Permanently」表示永久重定向,說明請求的資源已經(jīng)不存在了,需改用新的 URL 再次訪問。
- 「302 Found」表示臨時重定向,說明請求的資源還在,但暫時需要用另一個 URL 來訪問。
重定向是指將一個URL請求轉(zhuǎn)發(fā)到另一個URL的過程。重定向的作用包括:
- 更改URL:通過重定向,可以更改URL,使其更易于記憶、更友好或更有意義。例如,將長而復(fù)雜的URL重定向到簡潔的、易于理解的URL。
- 網(wǎng)站遷移:當(dāng)網(wǎng)站進(jìn)行重構(gòu)、更換域名或更改URL結(jié)構(gòu)時,通過重定向舊的URL到新的URL,可以讓用戶和搜索引擎正確地訪問和索引新的內(nèi)容。
Java八股
常用的集合有哪些?
圖片
List是有序的Collection,使用此接口能夠精確的控制每個元素的插入位置,用戶能根據(jù)索引訪問List中元素。常用的實(shí)現(xiàn)List的類有LinkedList,ArrayList,Vector,Stack。
- ArrayList是容量可變的非線程安全列表,其底層使用數(shù)組實(shí)現(xiàn)。當(dāng)幾何擴(kuò)容時,會創(chuàng)建更大的數(shù)組,并把原數(shù)組復(fù)制到新數(shù)組。ArrayList支持對元素的快速隨機(jī)訪問,但插入與刪除速度很慢。
- LinkedList本質(zhì)是一個雙向鏈表,與ArrayList相比,,其插入和刪除速度更快,但隨機(jī)訪問速度更慢。
Set不允許存在重復(fù)的元素,與List不同,set中的元素是無序的。常用的實(shí)現(xiàn)有HashSet,LinkedHashSet和TreeSet。
- HashSet通過HashMap實(shí)現(xiàn),HashMap的Key即HashSet存儲的元素,所有Key都是用相同的Value,一個名為PRESENT的Object類型常量。使用Key保證元素唯一性,但不保證有序性。由于HashSet是HashMap實(shí)現(xiàn)的,因此線程不安全。
- LinkedHashSet繼承自HashSet,通過LinkedHashMap實(shí)現(xiàn),使用雙向鏈表維護(hù)元素插入順序。
- TreeSet通過TreeMap實(shí)現(xiàn)的,添加元素到集合時按照比較規(guī)則將其插入合適的位置,保證插入后的集合仍然有序。
Map 是一個鍵值對集合,存儲鍵、值和之間的映射。Key 無序,唯一;value 不要求有序,允許重復(fù)。Map 沒有繼承于 Collection 接口,從 Map 集合中檢索元素時,只要給出鍵對象,就會返回對應(yīng)的值對象。主要實(shí)現(xiàn)有TreeMap、HashMap、HashTable、LinkedHashMap、ConcurrentHashMap
- HashMap:JDK1.8 之前 HashMap 由數(shù)組+鏈表組成的,數(shù)組是 HashMap 的主體,鏈表則是主要為了解決哈希沖突而存在的(“拉鏈法”解決沖突),JDK1.8 以后在解決哈希沖突時有了較大的變化,當(dāng)鏈表長度大于閾值(默認(rèn)為 8)時,將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時間
- LinkedHashMap:LinkedHashMap 繼承自 HashMap,所以它的底層仍然是基于拉鏈?zhǔn)缴⒘薪Y(jié)構(gòu)即由數(shù)組和鏈表或紅黑樹組成。另外,LinkedHashMap 在上面結(jié)構(gòu)的基礎(chǔ)上,增加了一條雙向鏈表,使得上面的結(jié)構(gòu)可以保持鍵值對的插入順序。同時通過對鏈表進(jìn)行相應(yīng)的操作,實(shí)現(xiàn)了訪問順序相關(guān)邏輯。
- HashTable:數(shù)組+鏈表組成的,數(shù)組是 HashMap 的主體,鏈表則是主要為了解決哈希沖突而存在的
- TreeMap:紅黑樹(自平衡的排序二叉樹)
- ConcurrentHashMap:Node數(shù)組+鏈表+紅黑樹實(shí)現(xiàn),線程安全的(jdk1.8以前Segment鎖,1.8以后volatile + CAS 或者 synchronized)
hashtable 和concurrentHashMap有什么區(qū)別
- 底層數(shù)據(jù)結(jié)構(gòu):
- jdk7之前的ConcurrentHashMap底層采用的是分段的數(shù)組+鏈表實(shí)現(xiàn),jdk8之后采用的是數(shù)組+鏈表/紅黑樹;
- HashTable采用的是數(shù)組+鏈表,數(shù)組是主體,鏈表是解決hash沖突存在的。
- 實(shí)現(xiàn)線程安全的方式:
- jdk8以前,ConcurrentHashMap采用分段鎖,對整個數(shù)組進(jìn)行了分段分割,每一把鎖只鎖容器里的一部分?jǐn)?shù)據(jù),多線程訪問不同數(shù)據(jù)段里的數(shù)據(jù),就不會存在鎖競爭,提高了并發(fā)訪問;jdk8以后,直接采用數(shù)組+鏈表/紅黑樹,并發(fā)控制使用CAS和synchronized操作,更加提高了速度。
- HashTable:所有的方法都加了鎖來保證線程安全,但是效率非常的低下,當(dāng)一個線程訪問同步方法,另一個線程也訪問的時候,就會陷入阻塞或者輪詢的狀態(tài)。
Java的垃圾回收器有哪些?
- Serial 收集器,串行收集器是最古老,最穩(wěn)定以及效率高的收集器,可能會產(chǎn)生較長的停頓,只使用一個線程去回收。
- ParNew 收集器,ParNew 收集器其實(shí)就是 Serial 收集器的多線程版本。
- Parallel 收集器,Parallel Scavenge 收集器類似 ParNew 收集器,Parallel 收集器更關(guān)注系統(tǒng)的吞吐量。
- Parallel Old 收集器,Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法
- CMS 收集器,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器。
- G1 收集器,G1 (Garbage-First)是一款面向服務(wù)器的垃圾收集器,主要針對配備多顆處理器及大容量內(nèi)存的機(jī)器. 以極高概率滿足 GC 停頓時間要求的同時,還具備高吞吐量性能特征
垃圾回收的方法有哪些?
- 標(biāo)記-清除算法:標(biāo)記-清除算法分為“標(biāo)記”和“清除”兩個階段,首先通過可達(dá)性分析,標(biāo)記出所有需要回收的對象,然后統(tǒng)一回收所有被標(biāo)記的對象。標(biāo)記-清除算法有兩個缺陷,一個是效率問題,標(biāo)記和清除的過程效率都不高,另外一個就是,清除結(jié)束后會造成大量的碎片空間。有可能會造成在申請大塊內(nèi)存的時候因?yàn)闆]有足夠的連續(xù)空間導(dǎo)致再次 GC。
- 復(fù)制算法:為了解決碎片空間的問題,出現(xiàn)了“復(fù)制算法”。復(fù)制算法的原理是,將內(nèi)存分成兩塊,每次申請內(nèi)存時都使用其中的一塊,當(dāng)內(nèi)存不夠時,將這一塊內(nèi)存中所有存活的復(fù)制到另一塊上。然后將然后再把已使用的內(nèi)存整個清理掉。復(fù)制算法解決了空間碎片的問題。但是也帶來了新的問題。因?yàn)槊看卧谏暾垉?nèi)存時,都只能使用一半的內(nèi)存空間。內(nèi)存利用率嚴(yán)重不足。
- 標(biāo)記-整理算法:復(fù)制算法在 GC 之后存活對象較少的情況下效率比較高,但如果存活對象比較多時,會執(zhí)行較多的復(fù)制操作,效率就會下降。而老年代的對象在 GC 之后的存活率就比較高,所以就有人提出了“標(biāo)記-整理算法”。標(biāo)記-整理算法的“標(biāo)記”過程與“標(biāo)記-清除算法”的標(biāo)記過程一致,但標(biāo)記之后不會直接清理。而是將所有存活對象都移動到內(nèi)存的一端。移動結(jié)束后直接清理掉剩余部分。
- 分代回收算法:分代收集是將內(nèi)存劃分成了新生代和老年代。分配的依據(jù)是對象的生存周期,或者說經(jīng)歷過的 GC 次數(shù)。對象創(chuàng)建時,一般在新生代申請內(nèi)存,當(dāng)經(jīng)歷一次 GC 之后如果對還存活,那么對象的年齡 +1。當(dāng)年齡超過一定值(默認(rèn)是 15,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)定)后,如果對象還存活,那么該對象會進(jìn)入老年代。
有哪些優(yōu)化java程序的辦法
調(diào)整新生代和老年代的比例、線程池、減少GC。
mysql
mysql幾種事務(wù)隔離
SQL 標(biāo)準(zhǔn)提出了四種隔離級別來規(guī)避這些現(xiàn)象,隔離級別越高,性能效率就越低,這四個隔離級別如下:
- 讀未提交(_read uncommitted_),指一個事務(wù)還沒提交時,它做的變更就能被其他事務(wù)看到;
- 讀提交(_read committed_),指一個事務(wù)提交之后,它做的變更才能被其他事務(wù)看到;
- 可重復(fù)讀(_repeatable read_),指一個事務(wù)執(zhí)行過程中看到的數(shù)據(jù),一直跟這個事務(wù)啟動時看到的數(shù)據(jù)是一致的,MySQL InnoDB 引擎的默認(rèn)隔離級別;
- 串行化(_serializable_ );會對記錄加上讀寫鎖,在多個事務(wù)對這條記錄進(jìn)行讀寫操作時,如果發(fā)生了讀寫沖突的時候,后訪問的事務(wù)必須等前一個事務(wù)執(zhí)行完成,才能繼續(xù)執(zhí)行;
按隔離水平高低排序如下:針對不同的隔離級別,并發(fā)事務(wù)時可能發(fā)生的現(xiàn)象也會不同。
也就是說:
- 在「讀未提交」隔離級別下,可能發(fā)生臟讀、不可重復(fù)讀和幻讀現(xiàn)象;
- 在「讀提交」隔離級別下,可能發(fā)生不可重復(fù)讀和幻讀現(xiàn)象,但是不可能發(fā)生臟讀現(xiàn)象;
- 在「可重復(fù)讀」隔離級別下,可能發(fā)生幻讀現(xiàn)象,但是不可能臟讀和不可重復(fù)讀現(xiàn)象;
- 在「串行化」隔離級別下,臟讀、不可重復(fù)讀和幻讀現(xiàn)象都不可能會發(fā)生。
臟讀和幻讀有什么區(qū)別
臟讀
如果一個事務(wù)「讀到」了另一個「未提交事務(wù)修改過的數(shù)據(jù)」,就意味著發(fā)生了「臟讀」現(xiàn)象。
舉個栗子。
假設(shè)有 A 和 B 這兩個事務(wù)同時在處理,事務(wù) A 先開始從數(shù)據(jù)庫中讀取小林的余額數(shù)據(jù),然后再執(zhí)行更新操作,如果此時事務(wù) A 還沒有提交事務(wù),而此時正好事務(wù) B 也從數(shù)據(jù)庫中讀取小林的余額數(shù)據(jù),那么事務(wù) B 讀取到的余額數(shù)據(jù)是剛才事務(wù) A 更新后的數(shù)據(jù),即使沒有提交事務(wù)。
因?yàn)槭聞?wù) A 是還沒提交事務(wù)的,也就是它隨時可能發(fā)生回滾操作,如果在上面這種情況事務(wù) A 發(fā)生了回滾,那么事務(wù) B 剛才得到的數(shù)據(jù)就是過期的數(shù)據(jù),這種現(xiàn)象就被稱為臟讀。
幻讀
在一個事務(wù)內(nèi)多次查詢某個符合查詢條件的「記錄數(shù)量」,如果出現(xiàn)前后兩次查詢到的記錄數(shù)量不一樣的情況,就意味著發(fā)生了「幻讀」現(xiàn)象。
舉個栗子。
假設(shè)有 A 和 B 這兩個事務(wù)同時在處理,事務(wù) A 先開始從數(shù)據(jù)庫查詢賬戶余額大于 100 萬的記錄,發(fā)現(xiàn)共有 5 條,然后事務(wù) B 也按相同的搜索條件也是查詢出了 5 條記錄。
接下來,事務(wù) A 插入了一條余額超過 100 萬的賬號,并提交了事務(wù),此時數(shù)據(jù)庫超過 100 萬余額的賬號個數(shù)就變?yōu)?6。
然后事務(wù) B 再次查詢賬戶余額大于 100 萬的記錄,此時查詢到的記錄數(shù)量有 6 條,發(fā)現(xiàn)和前一次讀到的記錄數(shù)量不一樣了,就感覺發(fā)生了幻覺一樣,這種現(xiàn)象就被稱為幻讀。
算法
有序數(shù)組1-100,刪除一個數(shù),如何查找
可以通過二分查找的方式來查找并刪除一個數(shù)。
首先,將數(shù)組從中間分成兩部分,比較中間元素和要查找的數(shù)的大小關(guān)系。如果中間元素等于要查找的數(shù),則刪除該元素并將數(shù)組整體向前移動一位。如果中間元素大于要查找的數(shù),則在左半部分繼續(xù)進(jìn)行二分查找。如果中間元素小于要查找的數(shù),則在右半部分繼續(xù)進(jìn)行二分查找。
重復(fù)以上步驟,直到找到要刪除的數(shù)或者確定該數(shù)不在數(shù)組中。如果找到要刪除的數(shù),則將該數(shù)刪除并將數(shù)組整體向前移動一位。
以下是java實(shí)現(xiàn)代碼:
public class DeleteNumber {
public static void main(String[] args) {
int[] arr = new int[100];
for (int i = 0; i < 100; i++) {
arr[i] = i + 1;
}
int target = 50; // 要刪除的數(shù)
int index = binarySearch(arr, target);
if(index != -1){
deleteNumber(arr, index);
System.out.println("刪除成功!");
}else{
System.out.println("未找到該數(shù)!");
}
}
public static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
public static void deleteNumber(int[] arr, int index) {
for (int i = index; i < arr.length - 1; i++) {
arr[i] = arr[i + 1];
}
}
}
在上面的代碼中,假設(shè)要刪除的數(shù)是50,然后調(diào)用binarySearch方法查找50的索引,如果找到則調(diào)用deleteNumber方法刪除該數(shù)。最后打印刪除成功的消息。