今天帶着一(yī / yì /yí)個(gè)滿滿的(de)幹貨主題和(hé / huò)大(dà)家見面——我們來(lái)聊聊在(zài)Java開發中常常會遇到(dào)的(de)面試題:“什麽叫線程安全?Servlet 是(shì)線程安全嗎?”有很多小夥伴在(zài)面試時(shí)都碰到(dào)過這(zhè)兩個(gè)問題,尤其是(shì)對剛剛踏入企業大(dà)門的(de)小夥伴,可能會對這(zhè)個(gè)問題感到(dào)有些困惑。别急,今天小米帶大(dà)家一(yī / yì /yí)步步解析,大(dà)家隻需跟着我一(yī / yì /yí)起走,保證面試官的(de)提問小菜一(yī / yì /yí)碟!
線程安全:你真的(de)懂了(le/liǎo)嗎?
首先,先讓我們來(lái)聊聊“線程安全”到(dào)底是(shì)個(gè)什麽概念?
假設你正在(zài)做一(yī / yì /yí)道(dào)數學題。你坐在(zài)書桌前,手拿一(yī / yì /yí)支筆,心無旁骛地(dì / de)開始解答。每當你寫下一(yī / yì /yí)個(gè)公式或者算出(chū)一(yī / yì /yí)個(gè)結果時(shí),桌面上(shàng)總是(shì)有一(yī / yì /yí)塊小闆子(zǐ)上(shàng)記錄着你的(de)步驟。這(zhè)個(gè)闆子(zǐ)上(shàng)有些許數據,你每做一(yī / yì /yí)步都會修改它,直到(dào)最終得出(chū)答案。如果旁邊有人(rén)跑過來(lái),突然拿起你的(de)闆子(zǐ)修改你正在(zài)記錄的(de)内容,那你很有可能就(jiù)會出(chū)現錯誤了(le/liǎo),對吧?
這(zhè)就(jiù)類似于(yú)“線程安全”的(de)問題。在(zài)多線程程序中,多個(gè)線程(可以(yǐ)理解爲(wéi / wèi)多個(gè)并發執行的(de)任務)共享同一(yī / yì /yí)塊内存空間(例如同一(yī / yì /yí)個(gè)對象或變量),如果多個(gè)線程同時(shí)對這(zhè)個(gè)共享資源進行修改,并且沒有适當的(de)同步控制,可能會導緻數據不(bù)一(yī / yì /yí)緻,進而(ér)産生難以(yǐ)預料的(de)錯誤。簡單來(lái)說(shuō),“線程安全”指的(de)就(jiù)是(shì):當多個(gè)線程同時(shí)訪問同一(yī / yì /yí)個(gè)共享資源時(shí),不(bù)會出(chū)現數據錯亂,程序也(yě)不(bù)會因此崩潰或産生邏輯錯誤。
線程不(bù)安全的(de)經典例子(zǐ)
我們通過一(yī / yì /yí)個(gè)簡單的(de)例子(zǐ)來(lái)感受一(yī / yì /yí)下什麽是(shì)線程不(bù)安全。假設有這(zhè)樣一(yī / yì /yí)個(gè)需求:模拟一(yī / yì /yí)個(gè)計數器,用于(yú)統計用戶訪問網站的(de)次數。如果我們直接寫一(yī / yì /yí)個(gè)簡單的(de)計數器類,代碼可能是(shì)這(zhè)樣的(de):
乍一(yī / yì /yí)看,這(zhè)個(gè)Counter類是(shì)完美的(de),可以(yǐ)直接用來(lái)統計訪問次數。但實際上(shàng),在(zài)多線程的(de)情況下,如果有多個(gè)線程同時(shí)訪問increment()方法,程序就(jiù)可能會出(chū)現線程安全問題。
假設有兩個(gè)線程同時(shí)執行increment(),它們都會從count中讀取值(假設是(shì)0),然後将值加1,最後寫回count。如果這(zhè)兩個(gè)線程的(de)執行順序是(shì)這(zhè)樣:
線程1讀取count爲(wéi / wèi)0
線程2讀取count爲(wéi / wèi)0
線程1将count加1并寫回,變成1
線程2也(yě)将count加1并寫回,結果是(shì)1
看吧,原本應該是(shì)2的(de)count,現在(zài)卻是(shì)1!這(zhè)就(jiù)是(shì)典型的(de)線程不(bù)安全問題。
解決線程安全問題的(de)辦法
在(zài)多線程環境中,爲(wéi / wèi)了(le/liǎo)避免這(zhè)種情況發生,我們通常需要(yào / yāo)通過某些方式來(lái)确保線程對共享資源的(de)訪問是(shì)互斥的(de),即同一(yī / yì /yí)時(shí)刻隻有一(yī / yì /yí)個(gè)線程能夠對共享資源進行操作,其他(tā)線程要(yào / yāo)麽等待,要(yào / yāo)麽避免直接修改。我們可以(yǐ)通過“鎖”來(lái)實現。
1. 使用synchronized關鍵字
最常見的(de)解決線程安全問題的(de)方法就(jiù)是(shì)通過synchronized關鍵字來(lái)實現。它的(de)作用是(shì)保證同一(yī / yì /yí)時(shí)刻隻有一(yī / yì /yí)個(gè)線程能訪問被修飾的(de)方法或代碼塊,從而(ér)保證線程安全。看一(yī / yì /yí)下修改後的(de)代碼:
在(zài)這(zhè)個(gè)版本中,我們給increment()方法加了(le/liǎo)synchronized關鍵字,确保了(le/liǎo)每次隻有一(yī / yì /yí)個(gè)線程能進入該方法。當一(yī / yì /yí)個(gè)線程執行increment()時(shí),其他(tā)線程需要(yào / yāo)等待該線程執行完畢,才能進入該方法,從而(ér)避免了(le/liǎo)線程安全問題。
2. 使用ReentrantLock
除了(le/liǎo)synchronized,Java還提供了(le/liǎo)更靈活的(de)ReentrantLock類,能夠對線程同步進行更細粒度的(de)控制。比如說(shuō),ReentrantLock允許在(zài)代碼塊中執行更複雜的(de)邏輯,并且可以(yǐ)嘗試在(zài)一(yī / yì /yí)定時(shí)間内獲取鎖。來(lái)看一(yī / yì /yí)下這(zhè)個(gè)實現:
在(zài)這(zhè)個(gè)版本中,lock.lock()和(hé / huò)lock.unlock()分别負責加鎖和(hé / huò)釋放鎖。通過這(zhè)種方式,我們可以(yǐ)靈活地(dì / de)控制鎖的(de)獲取和(hé / huò)釋放,同時(shí)确保線程安全。
Servlet 是(shì)否線程安全?
說(shuō)到(dào)這(zhè)裏,相信大(dà)家已經對線程安全有了(le/liǎo)初步的(de)理解。接下來(lái),我們進入本篇文章的(de)重點:Servlet 是(shì)線程安全嗎?
我們先來(lái)快速複習一(yī / yì /yí)下Servlet的(de)工作原理。Servlet是(shì)一(yī / yì /yí)個(gè)服務器端程序,用于(yú)響應客戶端的(de)請求。常見的(de)Servlet技術包括Java EE中的(de)HttpServlet類,它通常用于(yú)處理HTTP請求。Servlet的(de)生命周期由容器(比如Tomcat)管理,容器會爲(wéi / wèi)每個(gè)HTTP請求創建一(yī / yì /yí)個(gè)線程,并在(zài)該線程中調用Servlet的(de)service()方法。
1. Servlet實例化過程
通常,容器會在(zài)啓動時(shí)實例化Servlet,并且會将這(zhè)個(gè)實例用于(yú)後續的(de)請求。也(yě)就(jiù)是(shì)說(shuō),多個(gè)請求共享同一(yī / yì /yí)個(gè)Servlet實例。這(zhè)時(shí),如果Servlet内部存在(zài)共享資源(比如成員變量),就(jiù)可能會導緻線程安全問題。
例如,下面的(de)Servlet代碼就(jiù)不(bù)是(shì)線程安全的(de):
雖然這(zhè)段代碼看起來(lái)沒什麽問題,但在(zài)多線程環境下,多個(gè)請求可能會同時(shí)訪問doGet()方法。由于(yú)count是(shì)實例變量,它在(zài)多個(gè)請求之(zhī)間是(shì)共享的(de),因此可能會發生線程安全問題。
2. Servlet如何确保線程安全?
爲(wéi / wèi)了(le/liǎo)确保線程安全,我們可以(yǐ)采取以(yǐ)下幾種措施:
局部變量: 将共享的(de)count變量改爲(wéi / wèi)局部變量。由于(yú)局部變量在(zài)每個(gè)線程中都是(shì)獨立的(de),所以(yǐ)不(bù)會有線程安全問題。
使用同步: 可以(yǐ)使用synchronized關鍵字來(lái)确保count的(de)更新是(shì)線程安全的(de)。例如:
使用AtomicInteger: 如果隻是(shì)對數字類型的(de)變量進行線程安全的(de)更新,可以(yǐ)考慮使用java.util.concurrent.atomic.AtomicInteger類,它可以(yǐ)保證原子(zǐ)性地(dì / de)進行加法、減法等操作,避免了(le/liǎo)同步帶來(lái)的(de)性能損耗。
總結
今天我們從面試常見問題入手,講解了(le/liǎo)“什麽叫線程安全?”以(yǐ)及“Servlet 是(shì)否線程安全?”這(zhè)兩個(gè)問題。希望通過這(zhè)篇文章,大(dà)家能對線程安全的(de)概念有更深入的(de)理解,也(yě)能更好地(dì / de)應對工作中的(de)實際問題。
線程安全不(bù)僅是(shì)Java程序設計的(de)一(yī / yì /yí)個(gè)基礎知識,更是(shì)提高程序穩定性和(hé / huò)性能的(de)關鍵所在(zài)。在(zài)日常開發中,我們要(yào / yāo)時(shí)刻注意線程安全問題,尤其是(shì)在(zài)高并發場景下,更要(yào / yāo)小心謹慎。希望大(dà)家在(zài)面試中能夠遊刃有餘,成爲(wéi / wèi)一(yī / yì /yí)名能打硬仗的(de)Java開發工程師!
如果你對這(zhè)些知識點有疑問,或者有任何補充,歡迎在(zài)評論區留言,我們一(yī / yì /yí)起讨論。下次見!
