有些基礎題目由於工作中用的比較少但卻又是不可少的,這樣回答起來就會反應慢,不確定,不準確,特此開了文章記錄遇到的不確定或者回答比較拗口的問題。
1.servlet是單例的嗎,是安全的嗎,是多執行緒嗎
servlet是單例的,根據web.xml例項化一次後,其他訪問通過多執行緒的方式呼叫servlet例項。
因此,關於多執行緒訪問共享變數的安全性問題已經是老生常談了。這裡只要知道servlet是單例的,其他問題也就解決了。servlet的實現方式決定了安全性。成員變數是否是靜態的,是否上鎖?關於呼叫成員變數的方法中是否上鎖?或者是否是使用封裝線上程內的區域性變數?
2.什麼是執行緒安全?常用的HashMap,ArrayList是否安全?
執行緒安全問題的重點還是共享變數的問題,想了解關於共享變數的變化就要了解jmm(java memory model),簡單的說就是執行緒有工作區,變數放在記憶體堆中。執行緒工作必須copy一個副本到工作區去工作,這個操作叫做讀取。執行緒工作結束後將結果寫入記憶體。當多個執行緒讀和寫的時候就會有順序性問題。jvm的中讀寫無序性使得變數的實際值不確定,每個執行緒得到的變數的值在於它讀取的時候,而之後的時間內改變也不影響執行緒自己知道的值,即可見性問題。jvm中執行緒的工作區是互相不可見的。正是因為執行緒讀和寫是分兩步進行的,在這之間會發生的其他操作造成最終結果的不準確,這就是不安全的原因:原子性。只有保證讀寫操作是原子的才能保證變數的準確性,於是就是執行緒同步,即上鎖。可以使用synchronized和Lock,還有volatile。
HashMap和ArrayList不是執行緒安全的。可以使用併發包concurrent下的ConcurrentHashMap,此類採用分段寫鎖提高併發性,保證寫的安全性。同樣CopyOnWriteArrayList通過寫時上鎖並建立副本,在副本寫入後,通過volatile規則使得其他執行緒可見以及快取一致性,使得其他執行緒中的副本失效。
3.談談對java記憶體模型的瞭解
java memory model,jmm.
和上個問題差不多,主要是變數的儲存和賦值問題。在上篇文章的volatile有描述。
首先,java對變數的操作:讀取,計算,賦值都是線上程中實現的,變數是放在主記憶體(即記憶體),而計算的操作必須放線上程的工作區中(對應到硬體就是L1,L2以及暫存器)。執行緒之間的工作區只有執行緒自己持有,其他執行緒無法訪問也看不到。這是jmm對可見性的封裝。執行緒根據計算的時間不同而無法保證確切的寫入記憶體的時間,即“無序寫入”。java通過上鎖來保證原子操作,即原子性。java允許編譯器和處理器對指令進行重新排序,但是重排序過程不會影響到單執行緒程式的執行,卻會影響到多執行緒併發執行的正確性。,java記憶體模型具備一些先天的“有序性”,即不需要通過任何手段就能夠保證的有序性,這個通常也成為happens-before原則。如果兩個操作的執行次序無法從happens-before原則推匯出來,那麼她們就不能保證有序性,虛擬機器可以隨意地對她們進行重新排序。
- 程式次序規則:一個執行緒內,按照程式碼順序,書寫在前面的操作先行發生於書寫在後面的操作
- 鎖定規則:一個unlock操作先行發生於後面對同一個鎖lock操作。
- volatile變數規則:對一個變數的寫操作先行發生於後面對這個變數的讀操作。
- 傳遞規則:如果操作a執行緒發生於操作b,而操作b又先行發生於操作c,則可以得出操作a先行發生於操作c。
- 執行緒啟動規則:Thread物件的start()方法先行發生於此執行緒的每一個操作。
- 執行緒中斷規則:對於執行緒interrupt()方法的呼叫先行發生於被中斷執行緒的程式碼檢測到中斷時間的發生。
- 執行緒終結規則:執行緒中所有的操作都先行發生於執行緒的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到執行緒已經終止執行
- 物件終結規則:一個物件的初始化完成先行發生於他的finalize()方法的開始
- 這8條原則摘自《深入理解java虛擬機器》。前4條規則是比較重要,後4條顯而易見。
4.volatile有什麼用?能否用一句話說明下volatile的應用場景?
詳細見volatile。
volatile能保證可見性和一定的有序性。由於執行緒只在自己的工作區工作,如果另一個執行緒修改了變數的值,其他執行緒如果需要再次讀取變數的值的時候必須從主存中讀取。也就是說無法改變已經讀取了的執行緒,但保證了可見性和相對的有序性。另外,jvm的規則:volatile變數規則:對一個變數的寫操作先行發生於後面對這個變數的讀操作。這個可以保證語句的先後執行順序。程式碼中volatile標記的操作之前的程式碼必須執行完畢後才可執行。
應用場景:狀態標記量,用作執行緒run的條件,如果不用volatile則可能會不讀取記憶體標記,或者不知道何時讀取。程式碼順序保證,volatile標記的變數的操作之前的程式碼必須執行完畢。double check,雙重檢查。