您好,登錄后才能下訂單哦!
本篇內容主要講解“Java線程面試題的知識點有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“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 對象。
引入如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖、逃逸分析等技術來減少鎖操作的開銷。
逃逸分析:如果證明一個對象不會逃逸方法外或者線程外,則可針對此變量進行優化:
同步消除: synchronization Elimination,如果一個對象不會逃逸出線程,則對此變量的同步措施可消除。
鎖消除和粗化鎖消除:虛擬機的運行時編譯器在運行時如果檢測到一些要求同步的代碼上不可能發生共享數據競爭,則會去掉這些鎖。
鎖粗化:將臨近的代碼塊用同一個鎖合并起來。 消除無意義的鎖獲取和釋放,可以提高程序運行性能。
對象鎖是用于對象實例方法,或者一個對象實例上的,類鎖是用于類的靜態方法或者一個類的 class 對象上的。我們知道,類的對象實例可以有很多個,但是每個類只有一個 class 對象,所以不同對象實例的對象鎖是互不干擾的,但是每個類只有一個類鎖。
有一點必須注意的是,其實類鎖只是一個概念上的東西,并不是真實存 在的,類鎖其實鎖的是每個類的對應的 class 對象。類鎖和對象鎖之間也是互不干擾的。
不能保證,在 DCL 的作用是:volatile 是會保證被修飾的變量的可見性和有序性, 保證了單例模式下,保證在創建對象的時候的執行順序一定是
分配內存空間
實例化對象 instance
把 instance 引用指向已分配的內存空間,此時 instance 有了內存地址,不再為 null 了的步驟, 從而保證了 instance 要么為 null 要么是已經完全初始化好的對象。
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 種:
初始(NEW):新創建了一個線程對象,但還沒有調用 start()方法。
運行(RUNNABLE):Java 線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱為“運行”。 線程對象創建后,其他線程(比如 main 線程)調用了該對象的 start()方法。 該狀態的線程位于可運行線程池中,等待被線程調度選中,獲取 CPU 的使用權, 此時處于就緒狀態(ready)。就緒狀態的線程在獲得 CPU 時間片后變為運行中 狀態(running)。
阻塞(BLOCKED):表示線程阻塞于鎖。
等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作 (通知或中斷)。
超時等待(TIMED_WAITING):該狀態不同于 WAITING,它可以在指定的時 間后自行返回。
終止(TERMINATED):表示該線程已經執行完畢。
ThreadLocal 是 Java 里一種特殊的變量。ThreadLocal 為每個線程都提供了變量的副本,使得每個線程在某一時間訪問到的并非同一個對象,這樣就隔離了多個線程對數據的數據共享。
在內部實現上,每個線程內部都有一個 ThreadLocalMap,用來保存每個線程所擁有的變量副本。
在開發過程中,合理地使用線程池能夠帶來 3 個好處。
第一:降低資源消耗。
第二:提高響應速度。
第三:提高線程的可管理性。
如果當前運行的線程少于 corePoolSize,則創建新線程來執行任務(注意, 執行這一步驟需要獲取全局鎖)。
如果運行的線程等于或多于 corePoolSize,則將任務加入 BlockingQueue。
如果無法將任務加入 BlockingQueue(隊列已滿),則創建新的線程來處理任務。
如果創建新線程將使當前運行的線程超出 maximumPoolSize,任務將被拒絕,并調用 RejectedExecutionHandler.rejectedExecution()方法。
可以用 join 方法實現。
到此,相信大家對“Java線程面試題的知識點有哪些”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。