本篇文章為大家帶來了關於java的相關知識,其中主要整理了線程面試題的相關問題,包括了sychronied 修飾普通方法和靜態方法的區別、CAS 無鎖編程的原理、volatile 和synchronize 有什麼差別等等內容,下面一起來看一下,希望對大家有幫助。
推薦學習:《java影片教學》
物件鎖定是用於物件實例方法,或一個物件實例上的,類別鎖定是用於類別的靜態方法或一個類別的 class 物件上的。我們知道,類別的物件實例可以有很多個,但是每個類別只有一個 class 對象,所以不同物件實例的物件鎖是互不干擾的,但是每個類別只有一個類別鎖。
有一點必須注意的是,其實類別鎖定只是一個概念上的東西,並不是真實存 在的,類鎖其實鎖的是每個類別的對應的 class 物件。類鎖和物件鎖之間也是互不干擾的。
可見度是指當多個執行緒存取同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值。
由於線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存中的變量,那麼對於共享變量V,它們首先是在自己的工作內存,之後再同步到主內存。可是並不會及時的刷到主記憶體中,而是會有一定時間差。很明顯,這個時候線程 A 對變數 V 的操作對於線程 B 而言就不具備可見性了 。
要解決共享物件可視性這個問題,我們可以使用 volatile 關鍵字或是加鎖。
基本上使用目前的處理器支援CAS()的指令,只不過每個廠商所實現的演算法並不一樣,每一個CAS 作業過程都包含三個運算子:一個記憶體位址V,一個期望的值A 和一個新值B,操作的時候如果這個位址上存放的值等於這個期望的值A,則將位址上的值賦為新值B,否則不做任何操作。
CAS 的基本想法是,如果這個位址上的值和期望的值相等,則給予其新值, 否則不做任何事兒,但是要傳回原值是多少。循環 CAS 就是在一個循環裡不斷的 做 cas 操作,直到成功為止。還可以說說 CAS 的三大問題。
執行緒可以重複進入任何一個它已經擁有的鎖所同步著的程式碼區塊, synchronized、ReentrantLock 都是可重入的鎖定。在實現上,就是線程每次獲取鎖時判定如果獲得鎖的線程是它自己時,簡單將計數器累積即可,每釋放一次鎖, 進行計數器累減,直到計算器歸零,表示線程已經徹底釋放鎖。底層則是利用了 JUC 中的 AQS 來實現的。
是用來建構鎖定或其他同步元件的基礎架構,例如 ReentrantLock、 ReentrantReadWriteLock 和 CountDownLatch 是基於 AQS 實現的。它使用了一 個 int 成員變數表示同步狀態,透過內建的 FIFO 佇列來完成資源取得執行緒的排隊工作。它是 CLH 隊列鎖的變體實作。它可以實現 2 種同步方式:獨佔式,共 享式。
AQS 的主要使用方式是繼承,子類別透過繼承AQS 並實現它的抽象方法來管理同步狀態,同步器的設計是基於模板方法模式,所以如果要實現我們自己的同步工具類別就需要覆寫其中幾個可重寫的方法,如tryAcquire、tryReleaseShared 等等。
這樣設計的目的是同步元件(例如鎖定)是使用者導向的,它定義了使用者與同步元件互動的介面(例如可以允許兩個執行緒並行存取),隱藏了實作細節;同步器面向的是鎖的實現者,它簡化了鎖的實作方式,屏蔽了同步狀態管理、執行緒的排隊、等待與喚醒等底層操作。這樣就很好地隔離了使用者和實作者所需關注的領域。
在內部,AQS 維護一個共享資源 state,透過內建的 FIFO 來完成取得資源執行緒的排隊工作。這個佇列由一個一個的 Node 結點組成,每個 Node 結點維護一個 prev 引用和 next 引用,分別指向自己的前驅和後繼結點,構成一個雙端雙向鏈 表。
synchronized (this)原理:涉及兩個指令:monitorenter,monitorexit;再說同步方法,從同步方法反編譯的結果來看,方法的同步並沒有透過指令monitorenter 和monitorexit 來實現,相對於普通方法,其常數池中多了ACC_SYNCHRONIZED 標示符。
JVM 就是根據該標示符來實現方法的同步的:當方法被呼叫時,調用指令將會檢查方法的ACC_SYNCHRONIZED 訪問標誌是否被設置,如果設定了,執行線程將先獲取monitor,獲取成功之後獲取monitor,獲取成功之後獲取monitor才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其他任何執行緒都無法再取得同一個 monitor 物件。
引入如自旋鎖、適應性自旋鎖、鎖定消除、鎖定粗化、偏向鎖、輕量鎖定、逃逸分析等技術來減少鎖定操作的開銷。
有一點必須注意的是,其實類別鎖定只是一個概念上的東西,並不是真實存 在的,類鎖其實鎖的是每個類別的對應的 class 物件。類鎖和物件鎖之間也是互不干擾的。
volatile 是最輕量的同步機制。 volatile 保證了不同執行緒對這個變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。但是 volatile 不能保證操作的原子性,因此多執行緒下的寫入複合操作會導致執行緒安全性問題。
關鍵字synchronized 可以修飾方法或以同步區塊的形式來進行使用,它主要確保多個執行緒在同一個時刻,只能有一個執行緒處於方法或同步區塊中,它保證了執行緒對變數存取的可見性和排他性,又稱為內建鎖定機制。
Daemon(守護)線程是一種支援型線程,因為它主要被用作程式中後台調度以及支援性工作。這意味著,當一個 Java 虛擬機器中不存在非 Daemon 執行緒的時候,Java 虛擬機器將會退出。可以透過呼叫 Thread.setDaemon(true)將執行緒設定為 Daemon 執行緒。我們通常用不上,例如垃圾回收線程就是 Daemon 線程。
執行緒的中止:要麼是 run 執行完成了,要麼是拋出了一個未處理的例外狀況導致執行緒提前結束。暫停、恢復和停止操作對應在線程 Thread 的 API 就是 suspend()、resume()和 stop()。但是這些 API 是過期的,也就是不建議使用的。因為會導致程式可能工作在不確定狀態下。
安全的中止則是其他執行緒透過呼叫某個執行緒A 的interrupt()方法來中斷操作,中斷的執行緒則是透過執行緒透過方法isInterrupted()來判斷是否中斷,也可以呼叫靜態方法Thread.interrupted()來進行判斷目前執行緒是否被中斷,不過Thread.interrupted()會同時將中斷識別位改寫為false。
yield()方法:讓目前執行緒讓出 CPU 佔有權,但讓出的時間是無法設定的。也不會釋放鎖定資源。所有執行 yield()的執行緒有可能在進入就緒狀態後會被作業系統再次選取馬上又被執行。
yield() 、sleep()被呼叫後,都不會釋放目前執行緒所持有的鎖定。
呼叫 wait()方法後,會釋放目前執行緒所持有的鎖定,而且目前被喚醒後,會重新去競爭鎖定,鎖定競爭到後才會執行 wait 方法後面的程式碼。
Wait 通常用於線程間交互,sleep 通常用於暫停執行,yield()方法使當前執行緒讓出 CPU 佔有權。
wait 的執行緒使用 notify/notifyAll()進行喚醒。
sleep 本身支援中斷,如果執行緒在 sleep 期間中斷,則會拋出中斷例外。
Java 中執行緒的狀態分為 6 種:
ThreadLocal 是 Java 裡一種特殊的變數。 ThreadLocal 為每個線程都提供了變數的副本,使得每個線程在某一時間訪問到的並非同一個對象,這樣就隔離了多個線程對資料的資料共享。
在內部實作上,每個執行緒內部都有一個 ThreadLocalMap,用來保存每個執行緒所擁有的變數副本。
在開發過程中,合理地使用執行緒池能帶來 3 個好處。
第一:降低資源消耗。
第二:提高反應速度。
第三:提高執行緒的可管理性。
可用 join 方法實現。
推薦學習:《java影片教學》
以上是歸納整理Java線程面試題的詳細內容。更多資訊請關注PHP中文網其他相關文章!