1. Java 中實現(xiàn)多線程有幾種方法
繼承 Thread 類;
實現(xiàn) Runnable 接口;
實現(xiàn) Callable 接口通過 FutureTask 包裝器來創(chuàng)建 Thread 線程;
使 用 ExecutorService 、 Callable 、 Future 實 現(xiàn) 有 返 回 結 果 的多 線 程 ( 也 就 是 使 用 了
ExecutorService 來管理前面的三種方式)。
2. 如何停止一個正在運行的線程
1、使用退出標志,使線程正常退出,也就是當 run 方法完成后線程終止。
2、使用 stop 方法強行終止,但是不推薦這個方法,因為 stop 和 suspend 及 resume 一樣都
是過期作廢的方法。
3、使用 interrupt 方法中斷線程。
3. notify()和 和 notifyAll() 有什么區(qū)別?
notify 可能會導致死鎖,而 notifyAll 則不會
任何時候只有一個線程可以獲得鎖,也就是說只有一個線程可以運行 synchronized 中的代
碼
使用 notifyall,可以喚醒所有處于 wait 狀態(tài)的線程,使其重新進入鎖的爭奪隊列中,而 notify
只能喚醒一個。
wait() 應配合 while 循環(huán)使用,不應使用 if,務必在 wait()調用前后都檢查條件,如果不滿足,
必須調用 notify()喚醒另外的線程來處理,自己繼續(xù) wait()直至條件滿足再往下執(zhí)行。
notify() 是對 notifyAll()的一個優(yōu)化,但它有很精確的應用場景,并且要求正確使用。不然可
能導致死鎖。正確的場景應該是 WaitSet 中等待的是相同的條件,喚醒任一個都能正確處理
接下來的事項,如果喚醒的線程無法正確處理,務必確保繼續(xù) notify()下一個線程,并且自
身需要重新回到 WaitSet 中.
4. sleep()和 和 wait() 有什么區(qū)別?
對于 sleep()方法,我們首先要知道該方法是屬于 Thread 類中的。而 wait()方法,則是屬于
Object 類中的。
sleep()方法導致了程序暫停執(zhí)行指定的時間,讓出 cpu 該其他線程,但是他的監(jiān)控狀態(tài)依然
保持者,當指定的時間到了又會自動恢復運行狀態(tài)。在調用 sleep()方法的過程中,線程不會
釋放對象鎖。
當調用 wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此
對象調用 notify()方法后本線程才進入對象鎖定池準備,獲取對象鎖進入運行狀態(tài)。
5. volatile 是什么? 可以保證有序性嗎?
一旦一個共享變量(類的成員變量、類的靜態(tài)成員變量)被 volatile 修飾之后,那么就具備
了兩層語義:
1)保證了不同線程對這個變量進行操作時的可 性,即一個線程修改了某個變量的值,這新
值對其他線程來說是立即可 的,volatile 關鍵字會強制將修改的值立即寫入主存。
2)禁止進行指令重排序。
volatile 不是原子性操作
什么叫保證部分有序性?
當程序執(zhí)行到 volatile 變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經(jīng)進
行,且結果已經(jīng)對后面的操作可 ;在其后面的操作肯定還沒有進行;
x = 2; //語句 1
y = 0; //語句 2
flag = true; //語句 3
x = 4; //語句 4
y = -1; //語句 5
由于 flag 變量為 volatile 變量,那么在進行指令重排序的過程的時候,不會將語句 3 放到語
句 1、語句 2 前面,也不會講語句 3 放到語句 4、語句 5 后面。但是要注意語句 1 和語句 2
的順序、語句 4 和語句 5 的順序是不作任何保證的。
使用 Volatile 一般用于 狀態(tài)標記量 和 單例模式的雙檢鎖
6. Thread 類中的 start() 和 和 run() 方法有什么區(qū)別?
start()方法被用來啟動新創(chuàng)建的線程,而且 start()內部調用了 run()方法,這和直接調用 run()
方法的效果不一樣。當你調用 run()方法的時候,只會是在原來的線程中調用,沒有新的線
程啟動,start()方法才會啟動新線程。
7. 為什么 么 wait, notify 和 和 notifyAll 這些方法不在 在 thread 類 類
里面?
明顯的原因是 JAVA 提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。
如果線程需要等待某些鎖那么調用對象中的 wait()方法就有意義了。如果 wait()方法定義在
Thread 類中,線程正在等待的是哪個鎖就不明顯了。簡單的說,由于 wait,notify 和 notifyAll
都是鎖級別的操作,所以把他們定義在 Object 類中因為鎖屬于對象。
8. 為什么 wait 和 和 notify 方法要在同步塊中調用?
- 只有在調用線程擁有某個對象的獨占鎖時,才能夠調用該對象的 wait(),notify()和 notifyAll()
方法。 - 如果你不這么做,你的代碼會拋出 IllegalMonitorStateException 異常。
- 還有一個原因是為了避免 wait 和 notify 之間產(chǎn)生競態(tài)條件。
wait()方法強制當前線程釋放對象鎖。這意味著在調用某對象的 wait()方法之前,當前線程必
須已經(jīng)獲得該對象的鎖。因此,線程必須在某個對象的同步方法或同步代碼塊中才能調用該
對象的 wait()方法。
在調用對象的 notify()和 notifyAll()方法之前,調用線程必須已經(jīng)得到該對象的鎖。因此,必
須在某個對象的同步方法或同步代碼塊中才能調用該對象的 notify()或 notifyAll()方法。
調用 wait()方法的原因通常是,調用線程希望某個特殊的狀態(tài)(或變量)被設置之后再繼續(xù)執(zhí)
行。調用 notify()或 notifyAll()方法的原因通常是,調用線程希望告訴其他等待中的線程:“特
殊狀態(tài)已經(jīng)被設置”。這個狀態(tài)作為線程間通信的通道,它必須是一個可變的共享狀態(tài)(或變
量)。
9. Java 中 中 interrupted 和 和 isInterruptedd 方法的區(qū)別?
interrupted() 和 isInterrupted()的主要區(qū)別是前者會將中斷狀態(tài)清除而后者不會。Java 多線
程的中斷機制是用內部標識來實現(xiàn)的,調用 Thread.interrupt()來中斷一個線程就會設置中斷
標識為 true。當中斷線程調用靜態(tài)方法 Thread.interrupted()來檢查中斷狀態(tài)時,中斷狀態(tài)會
被清零。而非靜態(tài)方法 isInterrupted()用來查詢其它線程的中斷狀態(tài)且不會改變中斷狀態(tài)標
識。簡單的說就是任何拋出 InterruptedException 異常的方法都會將中斷狀態(tài)清零。無論如
何,一個線程的中斷狀態(tài)有有可能被其它線程調用中斷來改變。
10. Java 中 中 synchronized 和 和 ReentrantLock 有什么不同?
相似點:
這兩種同步方式有很多相似之處,它們都是加鎖方式同步,而且都是阻塞式的同步,也就是
說當如果一個線程獲得了對象鎖,進入了同步塊,其他訪問該同步塊的線程都必須阻塞在同
步塊外面等待,而進行線程阻塞和喚醒的代價是比較高的.
區(qū)別:
這兩種方式最大區(qū)別就是對于 Synchronized 來說,它是 java 語言的關鍵字,是原生語法層面
的互斥,需要 jvm 實現(xiàn)。而 ReentrantLock 它是 JDK 1.5 之后提供的 API 層面的互斥鎖,需要
lock()和 unlock()方法配合 try/finally 語句塊來完成。
11. 有三個線程 T1,T2,T3, 如何保證順序執(zhí)行?
在多線程中有多種方法讓線程按特定順序執(zhí)行,你可以用線程類的 join()方法在一個線程中
啟動另一個線程,另外一個線程完成該線程繼續(xù)執(zhí)行。為了確保三個線程的順序你應該先啟
動最后一個(T3 調用 T2,T2 調用 T1),這樣 T1 就會先完成而 T3 最后完成。
實際上先啟動三個線程中哪一個都行,
因為在每個線程的 run 方法中用 join 方法限定了三個線程的執(zhí)行順序。
12. SynchronizedMap 和 和 ConcurrentHashMap 有什么區(qū)別?
SynchronizedMap()和 Hashtable 一樣,實現(xiàn)上在調用 map 所有方法時,都對整個 map 進行同
步。而 ConcurrentHashMap 的實現(xiàn)卻更加精細,它對 map 中的所有桶加了鎖。所以,只要
有一個線程訪問map,其他線程就無法進入map,而如果一個線程在訪問ConcurrentHashMap
某個桶時,其他線程,仍然可以對 map 執(zhí)行某些操作。
所以,ConcurrentHashMap 在性能以及安全性方面,明顯比 Collections.synchronizedMap()更
加有優(yōu)勢。同時,同步操作精確控制到桶,這樣,即使在遍歷 map 時,如果其他線程試圖
對 map 進行數(shù)據(jù)修改,也不會拋出 ConcurrentModificationException。
13. 什么是線程安全
線程安全就是說多線程訪問同一代碼,不會產(chǎn)生不確定的結果。
在多線程環(huán)境中,當各線程不共享數(shù)據(jù)的時候,即都是私有(private)成員,那么一定是線
程安全的。但這種情況并不多 ,在多數(shù)情況下需要共享數(shù)據(jù),這時就需要進行適當?shù)耐?br> 控制了。
線程安全一般都涉及到 synchronized, 就是一段代碼同時只能有一個線程來操作 不然中間
過程可能會產(chǎn)生不可預制的結果。
如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。
如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,
就是線程安全的。
14. Thread 類中的 yield 方法有什么作用?
Yield 方法可以暫停當前正在執(zhí)行的線程對象,讓其它有相同優(yōu)先級的線程執(zhí)行。它是一個
靜態(tài)方法而且只保證當前線程放棄 CPU 占用而不能保證使其它線程一定能占用 CPU,執(zhí)行
yield()的線程有可能在進入到暫停狀態(tài)后 上又被執(zhí)行。
15. Java 線程池中 submit() 和 和 execute() 方法有什么區(qū)別?
兩個方法都可以向線程池提交任務,execute()方法的返回類型是 void,它定義在 Executor 接
口中,而submit()方法可以返回持有計算結果的Future對象,它定義在ExecutorService接口中,
它擴展了 Executor 接口,其它線程池類像 ThreadPoolExecutor 和ScheduledThreadPoolExecutor
都有這些方法。
16. 于 說一說自己對于 synchronized 關鍵字的了解
synchronized 關鍵字解決的是多個線程之間訪問資源的同步性,synchronized 關鍵字可以保
證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執(zhí)行。
另外,在 Java 早期版本中,synchronized 屬于重量級鎖,效率低下,因為監(jiān)視器鎖(monitor)
是依賴于底層的操作系統(tǒng)的 Mutex Lock 來實現(xiàn)的,Java 的線程是映射到操作系統(tǒng)的原生線
程之上的。如果要掛起或者喚醒一個線程,都需要操作系統(tǒng)幫忙完成,而操作系統(tǒng)實現(xiàn)線程
之間的切換時需要從用戶態(tài)轉換到內核態(tài),這個狀態(tài)之間的轉換需要相對比較 的時間,時
間成本相對較高,這也是為什么早期的 synchronized 效率低的原因。慶幸的是在 Java 6 之
后 Java 官方對從 JVM 層面對 synchronized 較大優(yōu)化,所以現(xiàn)在的 synchronized 鎖效率也
優(yōu)化得很不錯了。JDK1.6 對鎖的實現(xiàn)引入了大量的優(yōu)化,如自旋鎖、適應性自旋鎖、鎖消除、
鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。
17. 用 說說自己是怎么使用 synchronized 關鍵字 , 在項目中
嗎 用到了嗎 synchronized 關鍵字最主要的三種使用方式:
修飾實例方法: 作用于當前對象實例加鎖,進入同步代碼前要獲得當前對象實例的鎖
修飾靜態(tài)方法: 也就是給當前類加鎖,會作用于類的所有對象實例,因為靜態(tài)成員不屬于任
何一個實例對象,是類成員( static 表明這是該類的一個靜態(tài)資源,不管 new 了多少個對
象,只有一份)。所以如果一個線程 A 調用一個實例對象的非靜態(tài) synchronized 方法,而線
程 B 需要調用這個實例對象所屬類的靜態(tài) synchronized 方法,是允許的,不會發(fā)生互斥現(xiàn)
象,因為訪問靜態(tài) synchronized 方法占用的鎖是當前類的鎖,而訪問非靜態(tài) synchronized
方法占用的鎖是當前實例對象鎖。
修飾代碼塊: 指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。
總結: synchronized 關鍵字加到 static 靜態(tài)方法和 synchronized(class)代碼塊上都是是給
Class 類上鎖。synchronized 關鍵字加到實例方法上是給對象實例上鎖。盡量不要使用
synchronized(String a) 因為 JVM 中,字符串常量池具有緩存功能!
18. 什么是線程安全?Vector 是一個線程安全類嗎?
如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。
如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量 的值也和預期的是一樣
的,就是線程安全的。一個線程安全的計數(shù)器類的同一個實例對象在被多個線程使用的情況
下也不會出現(xiàn)計算失誤。很顯然你可以將集合類分 成兩組,線程安全和非線程安全的。
Vector 是用同步方法來實現(xiàn)線程安全的, 而和它相似的 ArrayList 不是線程安全的。
19. volatile 關鍵字的作用?
一旦一個共享變量(類的成員變量、類的靜態(tài)成員變量)被 volatile 修飾之后,那么就具備
了兩層語義:
保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值
對其他線程來說是立即可見的。
禁止進行指令重排序。
volatile 本質是在告訴 jvm 當前變量在寄存器(工作內存)中的值是不確定的,需要從主存
中讀取;synchronized 則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞
住。
volatile 僅能使用在變量級別;synchronized 則可以使用在變量、方法、和類級別的。
volatile 僅能實現(xiàn)變量的修改可 性,并不能保證原子性;synchronized 則可以保證變量的修
改可見性和原子性。
volatile 不會造成線程的阻塞;synchronized 可能會造成線程的阻塞。
volatile 標記的變量不會被編譯器優(yōu)化;synchronized 標記的變量可以被編譯器優(yōu)化。
20. 常用的線程池有哪些?
newSingleThreadExecutor:創(chuàng)建一個單線程的線程池,此線程池保證所有任務的執(zhí)行順序按
照任務的提交順序執(zhí)行。
newFixedThreadPool:創(chuàng)建固定大小的線程池,每次提交一個任務就創(chuàng)建一個線程,直到線
程達到線程池的最大大小。
newCachedThreadPool:創(chuàng)建一個可緩存的線程池,此線程池不會對線程池大小做限制,線
程池大小完全依賴于操作系統(tǒng)(或者說 JVM)能夠創(chuàng)建的最大線程大小。
newScheduledThreadPool:創(chuàng)建一個大小無限的線程池,此線程池支持定時以及周期性執(zhí)行
任務的需求。
newSingleThreadExecutor:創(chuàng)建一個單線程的線程池。此線程池支持定時以及周期性執(zhí)行任
務的需求。
21. 簡述一下你對線程池的理解
(如果問到了這樣的問題,可以展開的說一下線程池如何用、線程池的好處、線程池的啟動
策略)合理利用線程池能夠帶來三個好處。
第一:降低資源消耗。通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創(chuàng)建就能立即執(zhí)行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,
還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一的分配,調優(yōu)和監(jiān)控。
22. Java 程序是如何執(zhí)行的
我們日常的工作中都使用開發(fā)工具(IntelliJ IDEA 或 Eclipse 等)可以很方便的調試程序,或
者是通過打包工具把項目打包成 jar 包或者 war 包,放入 Tomcat 等 Web 容器中就可以
正常運行了,但你有沒有想過 Java 程序內部是如何執(zhí)行的?其實不論是在開發(fā)工具中運行
還是在 Tomcat 中運行,Java 程序的執(zhí)行流程基本都是相同的,它的執(zhí)行流程如下:
先把 Java 代碼編譯成字節(jié)碼,也就是把 .java 類型的文件編譯成 .class 類型的文件。這個
過程的大致執(zhí)行流程:Java 源代碼 -> 詞法分析器 -> 語法分析器 -> 語義分析器 -> 字符
碼生成器 -> 最終生成字節(jié)碼,其中任何一個節(jié)點執(zhí)行失敗就會造成編譯失敗;
把 class 文件放置到 Java 虛擬機,這個虛擬機通常指的是 Oracle 官方自帶的 Hotspot JVM;
Java 虛擬機使用類加載器(Class Loader)裝載 class 文件;
類加載完成之后,會進行字節(jié)碼效驗,字節(jié)碼效驗通過之后 JVM 解釋器會把字節(jié)碼翻譯成
機器碼交由操作系統(tǒng)執(zhí)行。但不是所有代碼都是解釋執(zhí)行的,JVM 對此做了優(yōu)化,比如,
以 Hotspot 虛擬機來說,它本身提供了 JIT(Just In Time)也就是我們通常所說的動態(tài)編譯
器,它能夠在運行時將熱點代碼編譯為機器碼,這個時候字節(jié)碼就變成了編譯執(zhí)行。
23. 于 說一說自己對于 synchronized 關鍵字的了解
synchronized 關鍵字解決的是多個線程之間訪問資源的同步性,synchronized 關鍵字可以保
證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執(zhí)行。
另外,在 Java 早期版本中,synchronized 屬于重量級鎖,效率低下,因為監(jiān)視器鎖(monitor)
是依賴于底層的操作系統(tǒng)的 Mutex Lock 來實現(xiàn)的,Java 的線程是映射到操作系統(tǒng)的原生線
程之上的。如果要掛起或者喚醒一個線程,都需要操作系統(tǒng)幫忙完成,而操作系統(tǒng)實現(xiàn)線程
之間的切換時需要從用戶態(tài)轉換到內核態(tài),這個狀態(tài)之間的轉換需要相對比較 的時間,時
間成本相對較高,這也是為什么早期的 synchronized 效率低的原因。慶幸的是在 Java 6 之
后 Java 官方對從 JVM 層面對 synchronized 較大優(yōu)化,所以現(xiàn)在的 synchronized 鎖效率也
優(yōu)化得很不錯了。JDK1.6 對鎖的實現(xiàn)引入了大量的優(yōu)化,如自旋鎖、適應性自旋鎖、鎖消除、
鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。
24. 下 講一下 synchronized 關鍵字的底層原理
synchronized 關鍵字底層原理屬于 JVM 層面。
synchronized 同步語句塊的實現(xiàn)使用的是 monitorenter 和 monitorexit 指令,其中
monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束
位置。 當執(zhí)行 monitorenter 指令時,線程試圖獲取鎖也就是獲取 monitor(monitor 對象存
在于每個 Java 對象的對象頭中,synchronized 鎖便是通過這種方式獲取鎖的,也是為什么
Java 中任意對象可以作為鎖的原因) 的持有權.當計數(shù)器為 0 則可以成功獲取,獲取后將鎖計
數(shù)器設為 1 也就是加 1。相應的在執(zhí)行 monitorexit 指令后,將鎖計數(shù)器設為 0,表明鎖被
釋放。如果獲取對象鎖失敗,那當前線程就要阻塞等待,直到鎖被另外一個線程釋放為止。
synchronized 修飾的方法并沒有 monitorenter 指令和 monitorexit 指令,取得代之的確實是
ACC_SYNCHRONIZED 標 識 , 該 標 識 指 明 了 該 方 法 是 一 個 同 步 方 法 , JVM 通 過 該
ACC_SYNCHRONIZED 訪問標志來辨別一個方法是否聲明為同步方法,從而執(zhí)行相應的同步調
用。
25. 為什么要用線程池?
線程池提供了一種限制和管理資源(包括執(zhí)行一個任務)。 每個線程池還維護一些基本統(tǒng)計
信息,例如已完成任務的數(shù)量。
使用線程池的好處:
降低資源消耗。 通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
提高響應速度。 當任務到達時,任務可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
提高線程的可管理性。 線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,還會
降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一的分配,調優(yōu)和監(jiān)控。
26. 實現(xiàn) Runnable 接口和 Callable 接口的區(qū)別
如果想讓線程池執(zhí)行任務的話需要實現(xiàn)的 Runnable 接口或 Callable 接口。 Runnable 接口或
Callable 接口實現(xiàn)類都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 執(zhí)行。兩者
的區(qū)別在于 Runnable 接口不會返回結果但是 Callable 接口可以返回結果。
備注: Executors 工具類可以實現(xiàn) Runnable 對象和 Callable 對象之間的相互轉換。
Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule)。
27. 執(zhí)行 execute() 方法和 submit() 方法的區(qū)別是什么呢?
- execute() 方法用于提交不需要返回值的任務,所以無法判斷任務是否被線程池執(zhí)行成功
與否;
2)submit()方法用于提交需要返回值的任務。線程池會返回一個 future 類型的對象,通過這
個 future 對象可以判斷任務是否執(zhí)行成功,并且可以通過 future 的 get()方法來獲取返回值,
get()方法會阻塞當前線程直到任務完成,而使用 get(long timeout,TimeUnit unit)方法則
會阻塞當前線程一段時間后立即返回,這時候有可能任務沒有執(zhí)行完。
28. 如何創(chuàng)建線程池
方式一:通過構造方法實現(xiàn)
方式二:通過 Executor 框架的工具類 Executors 來實現(xiàn) 我們可以創(chuàng)建三種類型的
ThreadPoolExecutor:
FixedThreadPool : 該方法返回一個固定線程數(shù)量的線程池。該線程池中的線程數(shù)量始終不
變。當有一個新的任務提交時,線程池中若有空閑線程,則立即執(zhí)行。若沒有,則新的任務
會被暫存在一個任務隊列中,待有線程空閑時,便處理在任務隊列中的任務。
SingleThreadExecutor: 方法返回一個只有一個線程的線程池。若多余一個任務被提交到該
線程池,任務會被保存在一個任務隊列中,待線程空閑,按先入先出的順序執(zhí)行隊列中的任
務。
CachedThreadPool: 該方法返回一個可根據(jù)實際情況調整線程數(shù)量的線程池。線程池的線
程數(shù)量不確定,但若有空閑線程可以復用,則會優(yōu)先使用可復用的線程。若所有線程均在工
作,又有新的任務提交,則會創(chuàng)建新的線程處理任務。所有線程在當前任務執(zhí)行完畢后,將
返回線程池進行復用。
29. 線程的阻塞和死亡
線程五狀態(tài):新建、就緒、執(zhí)行、阻塞、死亡;其中,阻塞狀態(tài)(Blocked)線程運行過程中,
可能由于各種原因進入阻塞狀態(tài):
1>線程通過調用 sleep 方法進入睡眠狀態(tài);
2>線程調用一個在 I/O 上被阻塞的操作,即該操作在輸入輸出操作完成之前不會返回
到它的調用者;
3>線程試圖得到一個鎖,而該鎖正被其他線程持有;
4>線程在等待某個觸發(fā)條件;
所謂阻塞狀態(tài)是正在運行的線程沒有運行結束,暫時讓出 CPU,這時其他處于就緒狀態(tài)的線
程就可以獲得 CPU 時間,進入運行狀態(tài)。
死亡狀態(tài)(Dead)
有兩個原因會導致線程死亡:
- run 方法正常退出而自然死亡,
- 一個未捕獲的異常終止了 run 方法而使線程猝死。
30. 線程安全性的五種類別
答:①. 不可變 – 不可變的對象一定是線程安全的,并且永遠也不需要額外的同步。Java 類
庫中大多數(shù)基本數(shù)值類如 Integer 、 String 和 BigInteger 都是不可變的。
②. 線程安全 – 線程安全的對象,由類的規(guī)格說明所規(guī)定的約束在對象被多個線程訪
問時仍然有效,不管運行時環(huán)境如何排列,線程都不需要任何額外的同步。這種線程安全性
保證是很嚴格的 – 許多類,如 Hashtable 或者 Vector 都不能滿足這種嚴格的定義。
③. 有條件的線程安全 – 有條件的線程安全類對于單獨的操作可以是線程安全的,但
是某些操作序列可能需要外部同步。條件線程安全的最常見的例子是遍歷由 Hashtable 或
者 Vector 或者返回的迭代器。
④. 線程兼容 – 線程兼容類不是線程安全的,但是可以通過正確使用同步而在并發(fā)環(huán)
境中安全地使用。這可能意味著用一個 synchronized 塊包圍每一個方法調用,或者創(chuàng)建一
個包裝器對象,其中每一個方法都是同步的(就像 Collections.synchronizedList() 一樣)。許多
常見的類是線程兼容的,如集合類 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、
或者 JDBC 類 Connection 和 ResultSet。
⑤. 線程對立 – 線程對立類是那些不管是否調用了外部同步都不能在并發(fā)使用時安全
地呈現(xiàn)的類。線程對立很少見,當類修改靜態(tài)數(shù)據(jù),而靜態(tài)數(shù)據(jù)會影響在其他線程中執(zhí)行的
其他類的行為,這時通常會出現(xiàn)線程對立。線程對立類的一個例子是調用 System.setOut() 的
類。
31. 簡單理解線程池技術
多線程技術主要解決處理器單元內多個線程執(zhí)行的問題,它可以顯著減少處理器單元的閑置
時間,增加處理器單元的吞吐能力。
32. 說一下 synchronized 原理
在 java 語言中存在兩種內建的 synchronized 語法:1、synchronized 語句;2、synchronized
方法。對于 synchronized 語句當 Java 源代碼被 javac 編譯成 bytecode 的時候,會在同步塊的
入口位置和退出位置分別插入 monitorenter 和 monitorexit 字節(jié)碼指令。而 synchronized 方法
則會被翻譯成普通的方法調用和返回指令如:invokevirtual、areturn 指令,在 VM 字節(jié)碼層面
并沒有任何特別的指令來實現(xiàn)被 synchronized 修飾的方法,而是在 Class 文件的方法表中將
該方法的 access_flags 字段中的 synchronized 標志位置 1,表示該方法是同步方法并使用調用
該方法的對象或該方法所屬的 Class 在 JVM 的內部對象表示 Class 做為鎖對象。
33. ? 多線程了解嗎? 、 什么是多線程、 什么是線程安全?
如何解決?
多線程: 在一個應用程序中,同時,有多個不同的執(zhí)行路徑。
線程安全:就是在多個線程共享同一個數(shù)據(jù)會受到其他線程的干擾。
如何解決: 使用線程同步技術, 用上鎖。 讓一個線程執(zhí)行完了,在讓另一個線程執(zhí)行。
34. 在 在 ava 中守護線程和本地線程區(qū)別?
java 中的線程分為兩種:守護線程( Daemon)和用戶線程( User) 。
任何線程都可以設置為守護線程和用戶線程,通過方法 Thread.setDaemon(bool on);true 則
把該線程設置為守護線程,反之則為用戶線程 。Thread.setDaemon()必須在 Thread.start()
之前調用,否則運行時會拋出異常。
兩者的區(qū)別:
唯一的區(qū)別是判斷虛擬機 (JVM)何時離開,Daemon 是為其他線程提供服務,如果全部的
User Thread 已經(jīng)撤離, Daemon 沒有可服務的線程, JVM 撤離。 也可以理解為守護線
程是 JVM 自動創(chuàng)建的線程(但不一定),用戶線程是程序創(chuàng)建的線程;比如 JVM 的垃圾回
收線程是一個守護線程,當所有線程已經(jīng)撤離,不再產(chǎn)生垃圾,守護線程自然就沒事可干了,
當垃圾回收線程是 Java 虛擬機上僅剩的線程時, Java 虛擬機會自動離開。
擴展: Thread Dump 打印出來的線程信息,含有 daemon 字樣的線程即為守護進程,可能
會有:服務守護進程、編譯守護 進 程 、windows 下的監(jiān)聽 Ctrl+break 的守護進程、
Finalizer 守護進程、引用處理 守 護進程、 GC 守護進程。
35. 線程與進程的區(qū)別?
進程是操作系統(tǒng)分配資源的最小單元,線程是操作系統(tǒng)調度的最小單元。
一個程序至少有一個進程,一個進程至少有一個線程。
36. 什么是多線程中的上下文切換?
多線程會共同使用一組計算機上的 CPU,而線程數(shù)大于給程序分配的 CPU 數(shù)量時,為了讓
各個線程都有執(zhí)行的機會,就需要輪轉使用 CPU。不同的線程切換使用 CPU 發(fā)生的切換數(shù)
據(jù)等就是上下文切換。
37. 死鎖與活鎖的區(qū)別,死鎖與饑餓的區(qū)別?
死鎖:是指兩個或兩個以上的進程(或線程)在執(zhí)行過程中,因爭奪資源而造成的一種互相
等待的現(xiàn)象,若無外力作用,它們都將無法推進下去。
產(chǎn)生死鎖的必要條件:
1、互斥條件:所謂互斥就是進程在某一時間內獨占資源。
2、請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
3、不剝奪條件:進程已獲得資源,在末使用完之前,不能強行剝奪。
4、循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關系。
活鎖:任務或者執(zhí)行者沒有被阻塞,由于某些條件沒有滿足,導致一直重復嘗試,失敗,嘗
試,失敗。
活鎖和死鎖的區(qū)別在于,處于活鎖的實體是在不斷的改變狀態(tài),所謂的“活”, 而處于死鎖
的實體表現(xiàn)為等待;活鎖有可能自行解開,死鎖則不能。
饑餓:一個或者多個線程因為種種原因無法獲得所需要的資源,導致一直無法執(zhí)行的狀態(tài)。
Java 中導致饑餓的原因 :
1、高優(yōu)先級線程吞噬所有的低優(yōu)先級線程的 CPU 時間。
2、線程被永久堵塞在一個等待進入同步塊的狀態(tài),因為其他線程總是能在它之前持續(xù)地對
該同步塊進行訪問。
3、線程在等待一個本身也處于永久等待完成的對象 (比如調用這個對象的 wait 方法),因
為其他線程總是被持續(xù)地獲得喚醒。
38. Java 中用到的線程調度算法是什么?
采用時間片輪轉的方式??梢栽O置線程的優(yōu)先級,會映射到下層的系統(tǒng)上面的優(yōu)先級上,如
非特別需要,盡量不要用,防止線程饑餓。
39. 在 什么是線程組,為什么在 Java 中不推薦使用?
ThreadGroup 類,可以把線程歸屬到某一個線程組中,線程組中可以有線程對象,也可以有
線程組,組中還可以有線程,這樣的組織結構有點類似于樹的形式。
為什么不推薦使用?因為使用有很多的安全隱患吧,沒有具體追究,如果需要使用,推薦使
用線程池。
40. 用 為什么使用 Executor 框架?
每次執(zhí)行任務創(chuàng)建線程 new Thread()比較 消 耗 性能,創(chuàng)建一個線程是比較耗時、耗資源
的。
調用 new Thread()創(chuàng)建的線程缺乏管理,被 稱 為野線程,而且可以無限制的創(chuàng)建,線程之
間的相互競爭會導致過多占用系統(tǒng)資源而導致系統(tǒng)癱瘓,還有線程之間的頻繁交替也會消耗
很多系統(tǒng)資源。
接使用 new Thread() 啟動的線程不利于擴展,比如定時執(zhí)行、定期執(zhí)行、定時定期執(zhí)行、
線程中斷等都不便實現(xiàn)。
41. 在 在 Java 中 中 Executor 和 和 Executors 的區(qū)別?
Executors 工具類的不同方法按照我們的需求創(chuàng)建了不同的線程池,來滿足業(yè)務的需求。
Executor 接口對象能執(zhí)行我們的線程任務 。
ExecutorService 接口繼承了 Executor 接口并進行了擴展,提供了更多的方法我們能獲得任
務執(zhí)行的狀態(tài)并且可以獲取任務的返回值。使用 ThreadPoolExecutor 可以創(chuàng)建自定義線程
池。
Future 表示異步計算的結果,他提供了檢查計算是否完成的方法,以等待計算的完成,并
可以使用 get()方法獲取計算的結果。
42. Java Concurrency API 的 中的 Lock 接口(Lock interface)是 是
什么?對比同步它有什么優(yōu)勢?
Lock 接口比同步方法和同步塊提供了更具擴展性的鎖操作 。
他們允許更靈活的結構,可以具有完全不同的性質,并且可以支持多個相關類的條件對象。
它的優(yōu)勢有:
- 可以使鎖更公平
- 可以使線程在等待鎖的時候響應中斷
- 可以讓線程嘗試獲取鎖,并在無法獲取鎖的時候立即返回或者等待一段時間可以在不同
的范圍,以不同的順序獲取和釋放鎖
整體上來說 Lock 是 synchronized 的擴展版,Lock 提供了無條件的、可輪詢的 (tryLock 方
法 )、定時的 (tryLock 帶參方法 )、可中斷的 (lockInterruptibly)、可多條件隊列的
(newCondition 方法 )鎖操作。 另外 Lock 的實現(xiàn)類基本都支持非公平鎖 (默 認 )和公平鎖,
synchronized 只支持非公平鎖,當然,在大部分情況下,非公平鎖是高效的選擇。
43. 是 什么是 Executors 框架?
Executor 框架是一個根據(jù)一組執(zhí)行策略調用,調度,執(zhí)行和控制的異步任務的框架。
無限制的創(chuàng)建線程會引起應用程序內存溢出。所以創(chuàng)建一個線程池是個更好的的解決方案,
因為可以限制線程的數(shù)量并且可以回收再利用這些線程。利用 Executors 框架可以非常方便
的創(chuàng)建一個線 程 池 。
44. 什么是阻塞隊列?阻塞隊列的實現(xiàn)原理是什么?如何
使用阻塞隊列來實現(xiàn)生產(chǎn)者- 消費者模型?
阻塞隊列( Blocking## ueue)是一個支持兩個附加操作的隊列。
這兩個附加的操作是:在隊列為空時,獲取元素的線程會等待隊列變?yōu)榉强?。當隊列滿時,
存儲元素的線程會等待隊列可用。
阻塞隊列常用于生產(chǎn)者和消費者的場景,生產(chǎn)者是往隊列里添加元素的線程,消費者是從隊
列里拿元素的線程。阻塞隊列就是生產(chǎn)者存放元素的容器,而消費者也只從容器里拿元素。
java 5 之后,可以使用阻塞隊列來實現(xiàn),此方式大大簡少了代碼量,使得多線程編程更加容
易,安全方面也有保障。
阻塞隊列使用最經(jīng)典的場景就是 socket 客戶端數(shù)據(jù)的讀取和解析,讀取數(shù)據(jù)的線程不斷將
數(shù)據(jù)放入隊列,然后解析線程不斷從隊列取數(shù)據(jù)解析。
45. 是 什么是 Callable 和 和 Future?
Callable 接口類似于 Runnable,從名字就可以看出來了,但是 Runnable 不會返回結果,并
且無法拋出返回結果的異常,而 Callable 功能更強大一些,被線程執(zhí)行后,可以返回值,
這個返回值可以被 Future 拿到,也就是說,F(xiàn)uture 可以拿到異步執(zhí)行任務的返回值。
可以認為是帶有回調的 Runnable。
Future 接口表示異步任務,是還沒有完成的任務給出的未來結果 。所以說 Callable 用于產(chǎn)
生結果, Future 用于獲取結果。
46. 是 什么是 FutureTask?用 使用 ExecutorService 啟動任務。
在 Java 并發(fā)程序中 FutureTask 表示一個可以取消的異步運算。 它有啟動和取消運算、查
詢運算是否完成和取回運算結果等方法。只有當運算完成的時候結果才能取回,如果運算尚
未完成 get 方法將會阻塞。一 個 FutureTask 對象可以對調用了 Callable 和 Runnable 的
對象進行包裝,由于 FutureTask 也是調用了 Runnable 接口所以它可以提交給 Executor 來
執(zhí)行。
47. 什么是并發(fā)容器的實現(xiàn)?
何為同步容器:可以簡單地理解為通過 synchronized 來實現(xiàn)同步的容器,如果有多個線程
調 用 同 步 容 器 的 方 法 , 它 們 將 會 串 行 執(zhí) 行 。 比 如 Vector , Hashtable , 以 及
Collections.synchronizedSet, synchronizedList 等方法返回的容器。
可以通過查看 Vector, Hashtable 等這些同步容器的實現(xiàn)代碼,可以看到這些容器實現(xiàn)線
程安全的方式就是將它們的狀態(tài)封裝起來,并在需要同步的方法上加上關鍵字 synchronized。
并發(fā)容器使用了與同步容器完全不同的加鎖策略來提供更高的并發(fā)性和伸縮性,例如在
ConcurrentHashMap 中采用了一種粒度更細的加鎖機制,可以稱為分段鎖,在這種鎖機制下,
允許任意數(shù)量的讀線程并發(fā)地訪問 map,并且執(zhí)行讀操作的線程和寫操作的線程也可以并
發(fā)的訪問 map,同時允許一定數(shù)量的寫操作線程并發(fā)地修改 map,所以它可以在并發(fā)環(huán)境
下實現(xiàn)更高的吞吐量。
48. 多線程同步和互斥有幾種實現(xiàn)方法,都是什么?
線程同步是指線程之間所具有的一種制約關系,一個線程的執(zhí)行依賴另一個線程的消息,當
它沒有得到另一個線程的消息時應等待,直到消息到達時才被喚醒。線程互斥是指對于共享
的進程系統(tǒng)資源,在各單個線程訪問時的排它性。當有若干個線程都要使用某一共享資源時,
任何時刻最多只允許一個線程去使用,其它要使用該資源的線程必須等待,直到占用資源者
釋放該資源。線程互斥可以看成是一種特殊的線程同步。
線程間的同步方法大體可分為兩類:用戶模式和內核模式。顧名思義,內核模式就是指利用
系統(tǒng)內核對象的單一性來進行同步,使用時需要切換內核態(tài)與用戶態(tài),而用戶模式就是不需
要切換到內核態(tài),只在用戶態(tài)完成操作。
用戶模式下的方法有:原子操作(例如一個單一的全局變量),臨界區(qū)。內核模式下的方法
有:事件,信號量,互斥量。
49. 什么是競爭條件?你怎樣發(fā)現(xiàn)和解決競爭?
當多個進程都企圖對共享數(shù)據(jù)進行某種處理,而最后的結果又取決于進程運行的順序時,則
我們認為這發(fā)生了競爭條件( race condition)。
50. 用 為什么我們調用 start()行 方法時會執(zhí)行 run() 方法 , 為什
用 么我們不能直接調用 run() 方法?
當你調用 start()方法時你將創(chuàng)建新的線程,并且執(zhí)行在 run()方法里的代碼。
但是如果你直接調用 run()方法,它不會創(chuàng)建新的線程也不會執(zhí)行調用線程的代碼,只會把
run 方法當作普通方法去執(zhí)行。
51. Java 中你怎樣喚醒一個阻塞的線程?
在 Java 發(fā)展史上曾經(jīng)使用 suspend()、resume()方法對于線程進行阻塞喚醒,但隨之出現(xiàn)很
多問題,比較典型的還是死鎖問題。
解決方案可以使用以對象為目標的阻塞,即 利 用 Object 類的 wait()和 notify()方法實現(xiàn)線
程阻塞。
首先,wait、notify 方法是針對對象的,調用任意對象的 wait()方法都將導致線程阻塞,阻
塞的同時也將釋放該對象的鎖,相應地,調用任意對象的 notify()方法則將隨機解除該對象
阻塞的線程,但它需要重新獲取改對象的鎖,直到獲取成功才能往下執(zhí)行;其次, wait、
notify 方法必須在 synchronized 塊或方法中被調用,并且要保證同步塊或方法的鎖對象與
調用 wait、 notify 方法的對象是同一個,如此一來在調用 wait 之前當前線程就已經(jīng)成功
獲取某對象的鎖,執(zhí)行 wait 阻塞后當前線程就將之前獲取的對象鎖釋放。
52. 在 在 Java 中 中 CycliBarriar 和 和 CountdownLatch 有什么
區(qū)別?
CyclicBarrier 可以重復使用,而 CountdownLatch 不能重復使用 。
Java 的 concurrent 包里面的 CountDownLatch 其實可以把它看作一個計數(shù)器,只不過這個
計數(shù)器的操作是原子操作,同時只能有一個線程去操作這個計數(shù)器,也就是同時只能有一個
線程去減這個計數(shù)器里面的值。
你可以向 CountDownLatch 對象設置一個初始的數(shù)字作為計數(shù)值,任何調用這個對象上的
await()方法都會阻塞,直到這個計數(shù)器的計數(shù)值被其他的線程減為 0 為止。
所以在當前計數(shù)到達零之前, await 方法會一直受阻塞。 之后,會釋放所有等待的線程,
await 的所有后續(xù)調用都將立即返回。這 種現(xiàn)象只出現(xiàn)一次 — — 計數(shù)無法被重置。 如
果需要重置計數(shù),請考慮使用 CyclicBarrier。
CyclicBarrier 一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點
(common barrier point)。在 涉及一組固定大小的線程的程序中,這些線程必須不時地互相
等待,此時 CyclicBarrier 很有用。 因為該 barrier 在釋放等待線程后可以重用,所以稱它
為循環(huán) 的 barrier。
53. 什么是不可變對象,它對寫并發(fā)應用有什么幫助?
不可變對象 (Immutable Objects)即對象一旦被創(chuàng)建它的狀態(tài)(對象的數(shù)據(jù),也即對象屬性值)
就不能改變,反之即為可變對象 (Mutable Objects)。
不可變對象的類即為不可變類 (Immutable Class)。Java 平臺類庫中包含許多不可變類,如
String、基本類型的包裝類、 BigInteger 和 BigDecimal 等。
不可變對象天生是線程安全的。它們的常量(域)是在構造函數(shù)中創(chuàng)建的。既然它們的狀態(tài)
無法修改,這些常量永遠不會變。
不可變對象永遠是線程安全的。
只有滿足如下狀態(tài),一個對象才是不可變的;它的狀態(tài)不能在創(chuàng)建后再被修改;所有域都是
final 類型;并且,
它被正確創(chuàng)建(創(chuàng)建期間沒有發(fā)生 this 引用的逸出)。
54. 什么是多線程中的上下文切換?
在上下文切換過程中,CPU 會停止處理當前運行的程序,并保存當前程序運行的具體位置
以便之后繼續(xù)運行。從這個角度來看,上下文切換有點像我們同時閱讀幾本書,在來回切換
書本的同時我們需要記住每本書當前讀到的頁碼。在程序中,上下文切換過程中的“頁碼”
信息是保存在進程控制塊(PCB)中的。PCB 還經(jīng)常被稱作 “ 切換楨 ” ( switchframe)。
“ 頁 碼 ” 信息會一直保存到 CPU 的內存中,直到他們被再次使用。
上下文切換是存儲和恢復 CPU 狀態(tài)的過程,它使得線程執(zhí)行能夠從中斷點恢復執(zhí)行。上下
文切換是多任務操作系統(tǒng)和多線程環(huán)境的基本特征。
55. Java 中用到的線程調度算法是什么?
計算機通常只有一個 CPU,在任意時刻只能執(zhí)行一條機器指令,每個線程只有獲得 CPU 的使
用權才能執(zhí)行指令.所謂多線程的并發(fā)運行,其實是指從宏觀上看,各個線程輪流獲得 CPU 的
使用權,分別執(zhí)行各自的任務.在運行池中,會有多個處于就緒狀態(tài)的線程在等待 CPU,JAVA
虛擬機的一項任務就是負責線程的調度,線程調度是指按照特定機制為多個線程分配 CPU
的使用權.
有兩種調度模型:分時調度模型和搶占式調度模型。
分時調度模型是指讓所有的線程輪流獲得 cpu 的使用權 ,并且平均分配每個線程占用的
CPU 的時間片這個也比較好理解。
java 虛擬機采用搶占式調度模型,是指優(yōu)先讓可運行池中優(yōu)先級高的線程占用 CPU,如果
可運行池中的線程優(yōu)先級相同,那么就隨機選擇一個線程,使其占用 CPU。處于運行狀態(tài)的
線程會一直運行,直至它不得不放棄 CPU。
56. 用 為什么使用 Executor 框架比使用應用創(chuàng)建和管理線
程好?
使用 Executor 線程池框架的原因:
1、每次執(zhí)行任務創(chuàng)建線程 new Thread()比 較 消耗性能,創(chuàng)建一個線程是比較耗時、耗資
源的。
2、調用 new Thread()創(chuàng)建的線程缺乏管理,被稱為野線程,而且可以無限制的創(chuàng)建,線程
之間的相互競爭會導致過多占用系統(tǒng)資源而導致系統(tǒng)癱瘓,還有線程之間的頻繁交替也會消
耗很多系統(tǒng)資源。
3、直接使用 new Thread() 啟動的線程不利于擴展,比如定時執(zhí)行、定期執(zhí)行、定時定期執(zhí)
行、線程中斷等都不便實現(xiàn)。
使用 Executor 線程池框架的優(yōu)點
1、能復用已存在并空閑的線程從而減少線程對象的創(chuàng)建從而減少了消亡線程的開銷。
2、可有效控制最大并發(fā)線程數(shù),提高系統(tǒng)資源使用率,同時避免過多資源競爭。
3、框架中已經(jīng)有定時、定期、單線程、并發(fā)數(shù)控制等功能。
綜上所述使用線程池框架 Executor 能更好的管理線程、提供系統(tǒng)資源使用率。
57. java 中有幾種方法可以實現(xiàn)一個線程?
- 繼承 Thread 類
- 實現(xiàn) Runnable 接口
- 實現(xiàn) Callable 接口,需要實現(xiàn)的是 call() 方法
58. 如何停止一個正在運行的線程?
使用共享變量的方式
在這種方式中,之所以引入共享變量,是因為該變量可以被多個執(zhí)行相同任務的線程用來作
為是否中斷的信號,通知中斷線程的執(zhí)行。
使用 interrupt 方法終止線程
如果一個線程由于等待某些事件的發(fā)生而被阻塞,又該怎樣停止該線程呢?這種情況經(jīng)常會
發(fā)生,比如當一個線程由于需要等候鍵盤輸入而被阻塞,或者調用 Thread.join()方法,或者
Thread.sleep() 方 法 , 在 網(wǎng) 絡 中 調 用 ServerSocket.accept() 方 法 , 或 者 調 用 了
DatagramSocket.receive()方法時,都有可能導致線程阻塞,使線程處于處于不可運行狀態(tài)時,
即使主程序中將該線程的共享變量設置為 true,但該線程此時根本無法檢查循環(huán)標志,當
然也就無法立即中斷。這 里我們給出的建議是,不要使用 stop()方法,而是使用 Thread 提
供的 interrupt()方法,因為該方法雖然不會中斷一個正在運行的線程,但是它可以使一個被
阻塞的線程拋出一個中斷異常,從而使線程提前結束阻塞狀態(tài),退出堵塞代碼。
59. notify()和 和 notifyAll() 有什么區(qū)別?
當一個線程進入 wait 之后,就必須等其他線程 notify/notifyall,使 用 notifyall,可以喚醒所
有處于 wait 狀態(tài)的線程,使其重新進入鎖的爭奪隊列中,而 notify 只能喚醒一個。
如果沒把握,建議 notifyAll,防止 notigy 因為信號丟失而造成程序異常。
60. 是 什么是 Daemon 線程?它有什么意義?
所謂后臺 (daemon)線程,是指在程序運行的時候在后臺提供一種通用服務的線程,并且這
個線程并不屬于程序中不可或缺的部分。因此,當所有的非后臺線程結束時,程序也就終止
了,同時會殺死進程中的所有后臺線程。反過來說,只要有任何非后臺線程還在運行,程序
就不會終止。必須在線程啟動之前調用 setDaemon()方法,才能把它設置為后臺線 程 。 注
意:后臺進程在不執(zhí)行 finally 子句的情況下就會終止其 run()方法。
比如: JVM 的垃圾回收線程就是 Daemon 線程, Finalizer 也是守護線程。
61. java 如何實現(xiàn)多線程之間的通訊和協(xié)作?
中斷和共享變量
62. 什么是可重入鎖(ReentrantLock )?
synchronized、 ReentrantLock 都是可重入的鎖,可重入鎖相對來說簡化了并發(fā)編程的開發(fā)。
63. 個 當一個線程進入某個對象的一個 synchronized 的實
例方法后,其它線程是否可進入此對象的其它方法?
如果其他方法沒有 synchronized 的話,其他線程是可以進入的。
所以要開放一個線程安全的對象時,得保證每個方法都是線程安全的。
64. 樂觀鎖和悲觀鎖的理解及如何實現(xiàn),有哪些實現(xiàn)方式?
悲觀鎖:總是假設最壞的情況,每次去拿數(shù)據(jù)的時候都認為別人會修改,所以每次在拿數(shù)據(jù)
的時候都會上鎖,這樣別人想拿這個數(shù)據(jù)就會阻塞直到它拿到鎖。傳統(tǒng)的關系型數(shù)據(jù)庫里邊
就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
再比如 Java 里面的同步原語 synchronized 關鍵字的實現(xiàn)也是悲觀鎖。
樂觀鎖:顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時候都認為別人不會修改,所以不會上鎖,
但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù),可以使用版本號等機制。
樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫提供的類似于
write_condition 機制,其實都是提供的樂觀鎖。 在 Java 中 java.util.concurrent.atomic 包下
面的原子變量類就是使用了樂觀鎖的一種實現(xiàn)方式 CAS 實現(xiàn)的。
樂觀鎖的實現(xiàn)方式:
1、使用版本標識來確定讀到的數(shù)據(jù)與提交時的數(shù)據(jù)是否一致。提交后修改版本標識,不一
致時可以采取丟棄和再次嘗試的策略。
2、 java 中的 Compare and Swap 即 CAS ,當多個線程嘗試使用 CAS 同時更新同一個變
量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程并不會被掛起,
而是被告知這次競爭中失敗,并可以再次嘗試。 CAS 操作中包含三個操作數(shù) —— 需要讀
寫的內存位置(V)、進行比較的預期原值(A)和擬寫入的新值(B)。如果內存位置 V 的值
與預期原值 A 相匹配,那么處理器會自動將該位置值更新為新值 B。否則處理器不做任何
操作。
65. SynchronizedMap 和 和 ConcurrentHashMap 有什么區(qū)
別?
SynchronizedMap 一次鎖住整張表來保證 線 程 安全,所以每次只能有一個線程來訪為
map。
ConcurrentHashMap 使用分段鎖來保證在多線程下的性能 。ConcurrentHashMap 中則是一
次鎖住一個桶 。 ConcurrentHashMap 默認將 hash 表分為 16 個桶,諸如 get,put,remove
等常用操作只鎖當前需要用到的桶 。這樣,原來只能一個線程進入,現(xiàn)在卻能同時有 16 個
寫線程執(zhí)行,并發(fā)性能的提升是顯而易見的。
另外 ConcurrentHashMap 使用了一種不同的迭代方式。 在這種迭代方式中,當 iterator 被
創(chuàng)建后集合再發(fā)生改變就不再是拋出 ConcurrentModificationException,取而代之的是在改變
時 new 新的數(shù)據(jù)從而不影響原有的數(shù)據(jù) ,iterator 完成后再將頭指針替換為新的數(shù)據(jù) ,
這樣 iterator 線程可以使用原來老的數(shù)據(jù),而寫線程也可以并發(fā)的完成改變。
66. CopyOnWriteArrayList 可以用于什么應用場景?
CopyOnWriteArrayList(免鎖容器 )的好處之一是當多個迭代器同時遍歷和修改這個列表時,
不會拋出 ConcurrentModificationException。 在 CopyOnWriteArrayList 中,寫入將導致創(chuàng)建
整個底層數(shù)組的副本,而源數(shù)組將保留在原地,使得復制的數(shù)組在被修改時,讀取操作可以
安全地執(zhí)行。
1、由于寫操作的時候,需要拷貝數(shù)組,會消耗內存,如果原數(shù)組的內容比較多的情況下,
可能導致 young gc 或者 full gc;
2、不能用于實時讀的場景,像拷貝數(shù)組、新增元素都需要時間,所以調用一個 set 操作后,
讀取到數(shù)據(jù)可能還是舊的 ,雖 然 CopyOnWriteArrayList 能做到最終一致性,但是還是沒法滿
足實時性要求;
CopyOnWriteArrayList 的思想
1、讀寫分離,讀和寫分開
2、最終一致性
3、使用另外開辟空間的思路,來解決并發(fā)沖突
67. 什么叫線程安全?servlet 是線程安全嗎?
線程安全是編程中的術語,指某個函數(shù)、函數(shù)庫在多線程環(huán)境中被調用時,能夠正確地處理
多個線程之間的共享變量,使程序功能正確完成。
Servlet 不是線程安全的,servlet 是單實例多線程的,當多個線程同時訪問同一個方法,是
不能保證共享變量的線程安全性的。
Struts2 的 action 是多實例多線程的,是線程安全的,每個請求過來都會 new 一個新的
action 分配給這個請求,請求完成后銷毀。
SpringMVC 的 Controller 是線程安全的嗎?不是的,和 Servlet 類似的處理流程 。
Struts2 好處是不用考慮線程安全問題; Servlet 和 SpringMVC 需要考慮線程安全問題,但
是性能可以提升不用處理太多的 gc,可以使用 ThreadLocal 來處理多線程的問題。
68. volatile 下 有什么用?能否用一句話說明下 volatile 的應
用場景?
volatile 保證內存可見性和禁止指令重排 。
volatile 用于多線程環(huán)境下的單次操作 (單次讀或者單次寫 )。
69. 為什么代碼會重排序?
在執(zhí)行程序時,為了提供性能,處理器和編譯器常常會對指令進行重排序,但是不能隨意重
排序,不是你想怎么排序就怎么排序,它需要滿足以下兩個條件:
- 在單線程環(huán)境下不能改變程序運行的結果;
- 存在數(shù)據(jù)依賴關系的不允許重排序
需要注意的是:重排序不會影響單線程環(huán)境的執(zhí)行結果,但是會破壞多線程的執(zhí)行語義。
70. 一個線程運行時發(fā)生異常會怎樣?
如果異常沒有被捕獲該線程將會停止執(zhí)行。Thread.UncaughtExceptionHandler 是用于處理未
捕獲異常造成線程突然中斷情況的一個內嵌接口。當一個未捕獲異常將造成線程中斷的時候
JVM 會 使 用 Thread.getUncaughtExceptionHandler() 來 查 詢 線 程 的
UncaughtExceptionHandler 并將線程和異常作為參數(shù)傳遞給 handler 的 uncaughtException()
方法進行處理 。
71. 如何在兩個線程間共享數(shù)據(jù)?
在兩個線程間共享變量即可實現(xiàn)共享。
一般來說,共享變量要求變量本身是線程安全的,然后在線程內使用的時候,如果有對共享
變量的復合操作,那么也得保證復合操作的線程安全性。
72. 為 什 么 wait, notify 和 和 notifyAll 這 些 方 法 不 在
thread 類里面?
一個很明顯的原因是 JAVA 提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線
程獲得。由 于 wait,notify 和 notifyAll 都是鎖級別的操作,所以把他們定義在 Object 類
中因為鎖屬于對象。
73. 是 什么是 ThreadLocal 變量?
ThreadLocal 是 Java 里一種特殊的變量 。每個線程都有一個 ThreadLocal 就是每個線程都
擁有了自己獨立的一個變量,競爭條件被徹底消除了。它是為創(chuàng)建代價高昂的對象獲取線程
安全的好方法,比如你可以用 ThreadLocal 讓 SimpleDateFormat 變成線程安全的,因為那
個類創(chuàng)建代價高昂且每次調用都需要創(chuàng)建不同的實例所以不值得在局部范圍使用它,如果為
每個線程提供一個自己獨有的變量拷貝,將大大提高效率。首先,通過復用減少了代價高昂
的對象的創(chuàng)建個數(shù)。其次,你在沒有使用高代價的同步或者不變性的情況下獲得了線程安全。
74. Java 中 中 interrupted 和 和 isInterrupted 方法的區(qū)別?
interrupt:
interrupt 方法用于中斷線程 。調 用該方法的線程的狀態(tài)為將被置為 ”中斷 ”狀態(tài) 。注
意:線程中斷僅僅是置線程的中斷狀態(tài)位,不會停止線程。需要用戶自己去監(jiān)視線程的狀態(tài)
為并做處理。支持線程中斷的方法(也就是線程中斷后會拋出 interruptedException 的方法)
就是在監(jiān)視線程的中斷狀態(tài),一旦線程的中斷狀態(tài)被置為“中斷狀態(tài)”,就會拋出中斷異常。
interrupted:
查詢當前線程的中斷狀態(tài),并且清除原狀態(tài)。如果一個線程被中斷了,第一次調
用 interrupted 則返回 true,第二次和后面的就返回 false 了。
isInterrupted:
僅僅是查詢當前線程的中斷狀態(tài)
75. 么 為什么 wait 和 和 notify 方法要在同步塊中調用?
Java API 強制要求這樣做,如果你不這么做,你的代碼會拋出 IllegalMonitorStateException 異
常 。 還有一個原因是為了避免 wait 和 notify 之間產(chǎn)生競態(tài)條件。
76. 為什么你應該在循環(huán)中檢查等待條件?
處于等待狀態(tài)的線程可能會收到錯誤警報和偽喚醒,如果不在循環(huán)中檢查等待條件,程序就
會在沒有滿足結束條件的情況下退出。
77. Java 中的同步集合與并發(fā)集合有什么區(qū)別?
同步集合與并發(fā)集合都為多線程和并發(fā)提供了合適的線程安全的集合,不過并發(fā)集合的可擴
展性更高。在 Java1.5 之前程序員們只有同步集合來用且在多線程并發(fā)的時候會導致爭用,
阻礙了系統(tǒng)的擴展性。 Java5 介紹了并發(fā)集合像 ConcurrentHashMap,不僅提供線程安全還
用鎖分離和內部分區(qū)等現(xiàn)代技術提高了可擴展性。
78. ? 什么是線程池? 為什么要使用它?
創(chuàng)建線程要花費昂貴的資源和時間,如果任務來了才創(chuàng)建線程那么響應時間會變長,而且一
個進程能創(chuàng)建的線程數(shù)有限。為了避免這些問題,在程序啟動的時候就創(chuàng)建若干線程來響應
處理,它們被稱為線程池,里面的線程叫工作線程。從 JDK1.5 開始, Java API 提供了
Executor 框架讓你可以創(chuàng)建不同的線程池 。
79. 怎么檢測一個線程是否擁有鎖?
在 java.lang.Thread 中有一個方法叫 holdsLock(),它返回 true 如果當且僅當當前線程擁有
某個具體對象的鎖。
80. 在 你如何在 Java 中獲取線程堆棧?
第一種方法:
kill -3 [java pid]
不會在當前終端輸出,它會輸出到代碼執(zhí)行的或指定的地方去。比如,kill -3 tomcat pid, 輸
出堆棧到 log 目錄下 。 Jstack [java pid]
這個比較簡單,在當前終端顯示,也可以重定向到指定文件中。
第二種方法:
-JvisualVM: Thread Dump
81. JVM 中哪個參數(shù)是用來控制線程的棧堆棧小的?
-Xss 每個線程的棧大小
82. Thread 的 類中的 yield 方法有什么作用?
使當前線程從執(zhí)行狀態(tài)(運行狀態(tài))變?yōu)榭蓤?zhí)行態(tài)(就緒狀態(tài))。
當前線程到了就緒狀態(tài),那么接下來哪個線程會從就緒狀態(tài)變成執(zhí)行狀態(tài)呢?可能是當前線
程,也可能是其他線程,看系統(tǒng)的分配了。
83. Java 中 中 ConcurrentHashMap 的并發(fā)度是什么?
ConcurrentHashMap 把實際 map 劃分成若干部分來實現(xiàn)它的可擴展性和線程安全。 這種
劃分是使用并發(fā)度獲得的,它是 ConcurrentHashMap 類構造函數(shù)的一個可選參數(shù),默認值
為 16,這樣在多線程情況下就能避免爭用。
在 JDK8 后,它摒棄了 Segment(鎖段)的概念,而是啟用了一種全新的方式實現(xiàn),利用 CAS
算法。同時加入了更多的輔助變量來提高并發(fā)度,具體內容還是查看源碼吧。
84. Java 中 中 Semaphore 是什么?
Java 中的 Semaphore 是一種新的同步類,它是一個計數(shù)信號 。 從概念上講,從概念上講,
信號量維護了一個許可集合。如有必要,在許可可用前會阻塞每一個 acquire(),然后再獲取
該許可 。每個 release()添加一個許可,從而可能釋放一個正在阻塞的獲取者。但 是,不使
用實際的許可對象,Semaphore 只對可用許可的號碼進行計數(shù),并采取相應的行動。信號
量常常用于多線程的代碼中,比如數(shù)據(jù)庫連接池。
85. Java 中 線程池中 submit() 和 和 execute() 方法有什么區(qū)
別?
兩個方法都可以向線程池提交任務,execute() 方法的返回類型是 void,它定義在 Executor
接口中 。
而 submit()方法可以返回持有計算結果的 Future 對象,它定義在 ExecutorService 接口中,
它 擴 展 了 Executor 接 口 , 其 它 線 程 池 類 像 ThreadPoolExecutor 和
ScheduledThreadPoolExecutor 都有這些方法 。
86. 什么是阻塞式方法?
阻塞式方法是指程序會一直等待該方法完成期間不做其他事情, ServerSocket 的 accept()
方法就是一直等待客戶端連接 。這里的阻塞是指調用結果返回之前,當前線程會被掛起,
直到得到結果之后才會返回。此外,還有異步和非阻塞式方法在任務完成前就返回。
87. Java 的 中的 ReadWriteLock 是什么?
讀寫鎖是用來提升并發(fā)程序性能的鎖分離技術的成果。
88. volatile 和 變量和 atomic 變量有什么不同?
Volatile 變量可以確保先行關系,即寫操作會發(fā)生在后續(xù)的讀操作之前 , 但它并不能保證原
子性。 例如用 volatile 修飾 count 變量那么 count++ 操作就不是原子性的。
而 AtomicInteger 類提供的 atomic 方法可以讓這種操作具有原子性如 getAndIncrement()方
法會原子性的進行增量操作把當前值加一,其它數(shù)據(jù)類型和引用變量也可以進行相似操作。
89. 用 可以直接調用 Thread 的 類的 run () 方法么?
可以。 但是如果我們調用了 Thread 的 run()方法,它的行為就會和普通的方法一樣,會在
當前線程中執(zhí)行。為了在新的線程中執(zhí)行我們的代碼,必須使用 Thread.start()方法 。
90. 如何讓正在運行的線程暫停一段時間?
我們可以使用 Thread 類的 Sleep()方法讓線程暫停一段時間。 需要注意的是,這并不會讓
線程終止,一旦從休眠中喚醒線程,線程的狀態(tài)將會被改變?yōu)?Runnable,并且根據(jù)線程調
度,它將得到執(zhí)行。
91. 你對線程優(yōu)先級的理解是什么?
每一個線程都是有優(yōu)先級的,一般來說,高優(yōu)先級的線程在運行時會具有優(yōu)先權,但這依賴
于線程調度的實現(xiàn),這個實現(xiàn)是和操作系統(tǒng)相關的 (OS dependent)。 我們可以定義線程的
優(yōu)先級,但是這并不能保證高優(yōu)先級的線程會在低優(yōu)先級的線程前執(zhí)行。 線程優(yōu)先級是一
個 int 變量 (從 1-10), 1 代表最低優(yōu)先級, 10 代表最高優(yōu)先級。
java 的線程優(yōu)先級調度會委托給操作系統(tǒng)去處理,所以與具體的操作系統(tǒng)優(yōu)先級有關,如
非特別需要,一般無需設置線程優(yōu)先級。
92. 什么是線程調度器(Thread Scheduler) 和時間分片(Time
Slicing )? ?
線程調度器是一個操作系統(tǒng)服務,它負責為 Runnable 狀態(tài)的線程分配 CPU 時間。
一旦我們創(chuàng)建一個線程并啟動它,它的執(zhí)行便依賴于線程調度器的實現(xiàn)。
同上一個問題,線程調度并不受到 Java 虛擬機控制,所以由應用程序來控制它是更好的選
擇(也就是說不要讓你的程序依賴于線程的優(yōu)先級)。
時間分片是指將可用的 CPU 時間分配給可用的 Runnable 線程的過程。分 配 CPU 時間可
以基于線程優(yōu)先級或者線程等待的時間。
93. 保 你如何確保 main()是 方法所在的線程是 Java 程序最后
結束的線程?
使用 Thread 類的 join()方法來確保所有程序創(chuàng)建的線程在 main()方法退出前結束。
94. 法 為什么線程通信的方法 wait(), notify()和 和 notifyAll()被 被
在 定義在 Object 類里?
Java 的每個對象中都有一個鎖 (monitor,也可以成為監(jiān)視器 ) 并且 wait(),notify()等方法
用于等待對象的鎖或者通知其他線程對象的監(jiān)視器可用。在 Java 的線程中并沒有可供任何
對象使用的鎖和同步器。這 就 是 為什么這些方法是 Object 類的一部分,這樣 Java 的每
一個類都有用于線程間 通 信的基本方法。
95. 么 為什么 wait(), notify()和 和 notifyAll () 必須在同步方法或
者同步塊中被調用?
當一個線程需要調用對象的 wait()方法的時候,這個線程必須擁有該對象的鎖,接著它就會
釋放這個對象鎖并進入等待狀態(tài)直到其他線程調用這個對象上的 notify() 方法。同 樣的,
當一個線程需要調用對象的 notify()方法時,它會釋放這個對象的鎖,以便其他在等待的線
程就可以得到這個對象鎖。由于所有的這些方法都需要線程持有對象的鎖,這樣就只能通過
同步來實現(xiàn),所以他們只能在同步方法或者同步塊中被調用。
96. 么 為什么 Thread 的 類的 sleep()和 和 yield () 方法是靜態(tài)
的?
Thread 類的 sleep()和 yield()方法將在當前正在執(zhí)行的線程上運行 。 所以在其他處于等待
狀態(tài)的線程上調用這些方法是沒有意義的。這就是為什么這些方法是靜態(tài)的。它們可以在當
前正在執(zhí)行的線程中工作,并避免程序員錯誤的認為可以在其他非運行線程調用這些方法。
97. 如何確保線程安全?
在 Java 中可以有很多方法來保證線程安全 — — 同步,使用原子類 (atomic concurrent
classes),實現(xiàn)并發(fā)鎖,使用 volatile 關鍵字,使用不變類和線程安全類。
98. 同步方法和同步塊,哪個是更好的選擇?
同步塊是更好的選擇,因為它不會鎖住整個對象(當然你也可以讓它鎖住整個對象)。同步
方法會鎖住整個對象,哪怕這個類中有多個不相關聯(lián)的同步塊,這通常會導致他們停止執(zhí)行
并需要等待獲得這個對象上的鎖。
同步塊更要符合開放調用的原則,只在需要鎖住的代碼塊鎖住相應的對象,這樣從側面來說
也可以避免死鎖。
99. 如何創(chuàng)建守護線程?
使用 Thread 類的 setDaemon(true)方法可以將線程設置為守護線程,需要注意的是,需要
在調用 start()方法前調用這個方法,否則會拋出 IllegalThreadStateException 異常 。
100. 是 什么是 Java Timer 類?如何創(chuàng)建一個有特定時間間
隔的任務?
java.util.Timer 是一個工具類,可以用于安排一個線程在未來的某個特定時間執(zhí)行。 Timer
類可以用安排一次性任務或者周期任務。
java.util.TimerTask 是一個實現(xiàn)了 Runnable 接口的抽象類,我們需要去繼承這個類來創(chuàng)建我
們自己的定時任務并使用 Timer 去安排它的執(zhí)行。
101. 并發(fā)編程三要素?
1、原子性
原子性指的是一個或者多個操作,要么全部執(zhí)行并且在執(zhí)行的過程中不被其他操作打斷,要
么就全部都不執(zhí)行。
2、可見性
可見性指多個線程操作一個共享變量時,其中一個線程對變量進行修改后,其他線程可以立
即看到修改的結果。
3、有序性
有序性,即程序的執(zhí)行順序按照代碼的先后順序來執(zhí)行。
102. 實現(xiàn)可見性的方法有哪些?
synchronized 或者 Lock:保證同一個時刻只有一個線程獲取鎖執(zhí)行代碼,鎖釋放之前把最
新的值刷新到主內存,實現(xiàn)可見性。
103. 多線程的價值?
1、發(fā)揮多核 CPU 的優(yōu)勢
多線程,可以真正發(fā)揮出多核 CPU 的優(yōu)勢來,達到充分利用 CPU 的目的,采用多線程的
方式去同時完成幾件事情而不互相干擾。
2、防止阻塞
從程序運行效率的角度來看,單核 CPU 不但不會發(fā)揮出多線程的優(yōu)勢,反而會因為在單核
CPU 上運行多線程導致線程上下文的切換,而降低程序整體的效率。但是單核 CPU 我們還
是要應用多線程,就是為了防止阻塞。試想,如果單核 CPU 使用單線程,那么只要這個線
程阻塞了,比方說遠程讀取某個數(shù)據(jù)吧,對端遲遲未返回又沒有設置超時時間,那么你的整
個程序在數(shù)據(jù)返回回來之前就停止運行了。多線程可以防止這個問題,多條線程同時運行,
哪怕一條線程的代碼執(zhí)行讀取數(shù)據(jù)阻塞,也不會影響其它任務的執(zhí)行。
3、便于建模
這是另外一個沒有這么明顯的優(yōu)點了。假設有一個大的任務 A,單線程編程,那么就要考
慮很多,建立整個程序模型比較麻煩。但是如果把這個大的任務 A 分解成幾個小任務,任
務 B、任務 C、任務 D,分別建立程序模型,并通過多線程分別運行這幾個任務,那就簡
單很多了。
104. 創(chuàng)建線程的有哪些方式?
1、繼承 Thread 類創(chuàng)建線程類
2、通過 Runnable 接口創(chuàng)建線程類
3、通過 Callable 和 Future 創(chuàng)建線程
4、通過線程池創(chuàng)建
105. 創(chuàng)建線程的三種方式的對比?
1、采用實現(xiàn) Runnable、 Callable 接口的方式創(chuàng)建多線程 。
優(yōu)勢是:
線程類只是實現(xiàn)了 Runnable 接口或 Callable 接口,還可以繼承其他類。
在這種方式下,多個線程可以共享同一個 target 對象,所以非常適合多個相同線程來處理
同一份資源的情況,從而可以將 CPU、代碼和數(shù)據(jù)分開,形成清晰的模型,較好地體現(xiàn)了
面向對象的思想。
劣勢是:
編程稍微復雜,如果要訪問當前線程,則必須使用 Thread.currentThread()方法。
2、使用繼承 Thread 類的方式創(chuàng)建多線程
優(yōu)勢是:
編寫簡單,如果需要訪問當前線程,則無需使用 Thread.currentThread()方法,直接使用 this
即可獲得當前線程。
劣勢是:
線程類已經(jīng)繼承了 Thread 類,所以不能再 繼 承 其他父類。
106. Java 線程具有五中基本狀態(tài)
1、新建狀態(tài)( New):當線程對象對創(chuàng)建后,即進入了新建狀態(tài),如:Thread t = new MyThread();
2、就緒狀態(tài)( Runnable):當調用線程對象的 start()方法( t.start();),線程即進入就緒狀
態(tài)。處于就緒狀態(tài)的線程,只是說明此線程已經(jīng)做好了準備,隨時等待 CPU 調度執(zhí)行,并
不是說執(zhí)行了 t.start()此線程立即就會執(zhí)行;
3、運行狀態(tài)( Running) :當 CPU 開始調度處于就緒狀態(tài)的線程時,此時線程才得以真
正執(zhí)行,即進入到運行狀態(tài)。注:就 緒狀態(tài)是進入到運行狀態(tài)的唯一入口,也就是說,線
程要想進入運行狀態(tài)執(zhí)行,首先必須處于就緒狀態(tài)中;
4、阻塞狀態(tài)( Blocked):處于運行狀態(tài)中的線程由于某種原因,暫時放棄對 CPU 的使用
權,停止執(zhí)行,此時進入阻塞狀態(tài),直到其進入到就緒狀態(tài),才 有機會再次被 CPU 調用
以進入到運行狀態(tài)。
根據(jù)阻塞產(chǎn)生的原因不同,阻塞狀態(tài)又可以分為三種:
等待阻塞:運行狀態(tài)中的線程執(zhí)行 wait()方法,使本線程進入到等待阻塞狀態(tài);
同步阻塞:線程在獲取 synchronized 同 步 鎖失敗 (因為鎖被其它線程所占用 ),它會進入
同步阻塞狀態(tài);
其他阻塞:通過調用線程的 sleep()或 join() 或發(fā)出了 I/O 請求時,線程會進入到阻塞狀態(tài)。
當 sleep()狀態(tài)超時、 join()等待線程終止或者超時、或者 I/O 處理完畢時,線程重新轉入
就緒狀態(tài)。
5、死亡狀態(tài)( Dead):線程執(zhí)行完了或者因異常退出了 run()方法,該線程結束生命周期。
107. 什么是線程池?有哪幾種創(chuàng)建方式?
線程池就是提前創(chuàng)建若干個線程,如果有任務需要處理,線程池里的線程就會處理任務,處
理完之后線程并不會被銷毀,而是等待下一個任務。由于創(chuàng)建和銷毀線程都是消耗系統(tǒng)資源
的,所以當你想要頻繁的創(chuàng)建和銷毀線程的時候就可以考慮使用線程池來提升系統(tǒng)的性能。
java 提供了一個 java.util.concurrent.Executor 接口的實現(xiàn)用于創(chuàng)建線程池 。
108. 四種線程池的創(chuàng)建
1、 newCachedThreadPool 創(chuàng)建一個可緩存線程池
2、 newFixedThreadPool 創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù) 。
3、 newScheduledThreadPool 創(chuàng)建一個定長線程池,支持定時及周期性任務執(zhí)行。
4、 newSingleThreadExecutor 創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行
任務。
109. 線程池的優(yōu)點?
1、重用存在的線程,減少對象創(chuàng)建銷毀的開銷。
2、可有效的控制最大并發(fā)線程數(shù),提高系統(tǒng)資源的使用率,同時避免過多資源競爭,避免
堵塞。
3、提供定時執(zhí)行、定期執(zhí)行、單線程、并發(fā)數(shù)控制等功能。
110. 常用的并發(fā)工具類有哪些?
1、 CountDownLatch
2、 CyclicBarrier
3、 Semaphore
4、 Exchanger
111. CyclicBarrier 和 和 CountDownLatch 的區(qū)別
1、CountDownLatch 簡單的說就是一個線程等待,直到他所等待的其他線程都執(zhí)行完成并且
調用 countDown()方法發(fā)出通知后,當前線程才可以繼續(xù)執(zhí)行。
2、 cyclicBarrier 是所有線程都進行等待,直到所有線程都準備好進入 await()方法之后,所
有線程同時開始執(zhí)行!
3、 CountDownLatch 的計數(shù)器只能使用一次 。 而 CyclicBarrier 的計數(shù)器可以使用 reset()
方法重置。 所以 CyclicBarrier 能處理更為復雜的業(yè)務場景,比如如果計算發(fā)生錯誤,可以
重置計數(shù)器,并讓線程們重新執(zhí)行一次。
4、 CyclicBarrier 還提供其他有用的方法,比如
getNumberWaiting 方法可以獲得
CyclicBarrier 阻塞的線程數(shù)量。isBroken 方法用來知道阻塞的線程是否被中斷。如果被中斷
返回 true,否則返回 false。
112. synchronized 的作用?
在 Java 中, synchronized 關鍵字是用來控制線程同步的,就是在多線程的環(huán)境下,控制
synchronized 代碼段不被多個線 程 同 時執(zhí)行。 synchronized 既可以加在一段代碼上,也
可 以 加在方法上 。
113. volatile 關鍵字的作用
對于可見性, Java 提供了 volatile 關鍵字來保證可見性。
當一個共享變量被 volatile 修飾時,它會保證修改的值會立即被更新到主存,當有其他線程
需要讀取時,它會去內存中讀取新值。
從實踐角度而言, volatile 的一個重要作用就是和 CAS 結合,保證了原子性,詳細的可以
參見 java.util.concurrent.atomic 包下的類,比如 AtomicInteger。
114. 是 什么是 CAS
CAS 是 compare and swap 的縮寫,即我們所說的比較交換 。
cas 是一種基于鎖的操作,而且是樂觀鎖 。 在 java 中鎖分為樂觀鎖和悲觀鎖 。 悲觀鎖
是將資源鎖住,等一個之前獲得鎖的線程釋放鎖之后,下一個線程才可以訪問。而樂觀鎖采
取了一種寬泛的態(tài)度,通過某種方式不加鎖來處理資源,比如通過給記錄加 version 來獲取
數(shù)據(jù),性能較悲觀鎖有很大的提高。
CAS 操作包含三個操作數(shù) —— 內存位置(V)、預期原值(A)和新值(B)。如果內存地址里
面的值和 A 的值是一樣的,那么就將內存里面的值更新成 B。CAS 是通過無限循環(huán)來獲取
數(shù)據(jù)的,若果在第一輪循環(huán)中,a 線程獲取地址里面的值被 b 線程修改了,那么 a 線程需
要自旋,到下次循環(huán)才有可能機會執(zhí)行。 java.util.concurrent.atomic 包下的類大多是使用
CAS 操作來實現(xiàn)的 ( AtomicInteger,AtomicBoolean,AtomicLong)。
115. CAS 的問題
1、CAS 容易造成 ABA 問題
一個線程 a 將數(shù)值改成了 b,接著又改成了 a , 此時 CAS 認為是沒有變化,其實是已經(jīng)
變化過了,而這個問題的解決方案可以使用版本號標識,每操作一次 version 加 1。在 java5
中,已經(jīng)提供了 AtomicStampedReference 來解決問題 。
2、不能保證代碼塊的原子性
CAS 機制所保證的知識一個變量的原子性操作,而不能保證整個代碼塊的原子性。
比如需要保證 3 個變量共同進行原子性的更新,就不得不使用 synchronized 了。
3、CAS 造成 CPU 利用率增加
之前說過了 CAS 里面是一個循環(huán)判斷的過程,如果線程一直沒有獲取到狀態(tài),cpu 資源會
一直被占用。
116. 是 什么是 Future? ?
在并發(fā)編程中,我們經(jīng)常用到非阻塞的模型,在之前的多線程的三種實現(xiàn)中,不管是繼承
thread 類還是實現(xiàn) runnable 接口,都無法保證獲取到之前的執(zhí)行結果。通過實現(xiàn) Callback
接口,并用 Future 可以來接收多線程的執(zhí)行結果。
Future 表示一個可能還沒有完成的異步任務的結果,針對這個結果可以添加 Callback 以便
在任務執(zhí)行成功或失敗后作出相應的操作 。
117. 是 什么是 A## S
A## S 是 Abustact## ueuedSynchronizer 的簡稱,它是一個 Java 提高的底層同步工具類,用一
個 int 類型的變量表示同步狀態(tài),并提供了一系列的 CAS 操作來管理這個同步狀態(tài)。
A## S 是一個用來構建鎖和同步器的框架,使用 A## S 能簡單且高效地構造出應用廣泛的大量
的同步器,比如我們提到的 ReentrantLock,Semaphore,其他的諸如 ReentrantReadWriteLock,
Synchronous## ueue, FutureTask 等等皆是基于 A## S 的。
118. A## S 支持兩種同步方式
1、獨占式
2、共享式
這樣方便使用者實現(xiàn)不同類型的同步組件,獨占式如 ReentrantLock,共享式如 Semaphore,
CountDownLatch,組合式的如 ReentrantReadWriteLock??傊?, A## S 為使用提供了底層支
撐,如何組裝實現(xiàn),使用者可以自由發(fā)揮。
119. ReadWriteLock 是什么
首先明確一下,不是說 ReentrantLock 不好,只是 ReentrantLock 某些時候有局限。 如果
使用 ReentrantLock,可能本身是為了防止線程 A 在寫數(shù)據(jù)、線程 B 在讀數(shù)據(jù)造成的數(shù)據(jù)
不一致,但這樣,如果線程 C 在讀數(shù)據(jù)、線程 D 也在讀數(shù)據(jù),讀數(shù)據(jù)是不會改變數(shù)據(jù)的,
沒有必要加鎖,但是還是加鎖了,降低了程序的性能。因為這個,才誕生了讀寫鎖
ReadWriteLock 。 ReadWriteLock 是 一 個 讀 寫 鎖 接 口 , ReentrantReadWriteLock 是
ReadWriteLock 接口的一個具體實現(xiàn),實現(xiàn)了讀寫的分離,讀鎖是共享的,寫鎖是獨占的,
讀和讀之間不會互斥,讀和寫、寫和讀、寫和寫之間才會互斥,提升了讀寫的性能。
120. FutureTask 是什么
這個其實前面有提到過,F(xiàn)utureTask 表示一個異步運算的任務。FutureTask 里面可以傳入一
個 Callable 的具體實現(xiàn)類,可以對這個異步運算的任務的結果進行等待獲取、判斷是否已
經(jīng)完成、取消任務等操作。 當然,由于 FutureTask 也是 Runnable 接口的實現(xiàn)類,所以
FutureTask 也可以放入線程池中 。
121. synchronized 和 和 ReentrantLock 的區(qū)別
synchronized 是和 if、 else、 for、 while 一 樣 的關鍵字, ReentrantLock 是類,這是二
者的本質區(qū)別。 既然 ReentrantLock 是 類,那么它就提供了比 synchronized 更多更靈活
的特性,可以被繼承、可以有方法、可以有各種各樣的類變量, ReentrantLock 比
synchronized 的擴展性體現(xiàn)在幾點上:
1、 ReentrantLock 可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖 2、 ReentrantLock
可以獲取各種鎖的信息 3、 ReentrantLock 可以靈活地實現(xiàn)多路通知
另外,二者的鎖機制其實也是不一樣的。ReentrantLock 底層調用的是 Unsafe 的 park 方法
加鎖, synchronized 操作的應該是對象頭中 mark word。
122. 程 線程 B 程 怎么知道線程 A 修改了變量
1、 volatile 修飾變量
2、 synchronized 修飾修改變量的方法
3、 wait/notify
4、 while 輪詢
123. synchronized 、volatile 、CAS 比較
1、 synchronized 是悲觀鎖,屬于搶占式,會引起其他線程阻塞 。
2、 volatile 提供多線程共享變量可見性和禁止指令重排序優(yōu)化 。
3、CAS 是基于沖突檢測的樂觀鎖(非阻塞)
124. sleep 和 方法和 wait 方法有什么區(qū)別?
sleep 方法和 wait 方法都可以用來放棄 CPU 一定的時間,不同點在于如果線程持有某個
對象的監(jiān)視器, sleep 方法不會放棄這個對象的監(jiān)視器, wait 方法會放棄這個對象的監(jiān)視
器
125. ThreadLocal 是什么?有什么用?
ThreadLocal 是一個本地線程副本變量工具類 。 主要用于將私有線程和該線程存放的副本
對象做一個映射,各個線程之間的變量互不干擾,在高并發(fā)場景下,可以實現(xiàn)無狀態(tài)的調用,
特別適用于各個線程依賴不通的變量值完成操作的場景。簡單說 ThreadLocal 就是一種以空
間 換 時 間 的 做 法 , 在 每 個 Thread 里 面 維 護 了 一 個 以 開 地 址 法 實 現(xiàn) 的
ThreadLocal.ThreadLocalMap,把數(shù)據(jù)進行隔離,數(shù)據(jù)不共享,自然就沒有線程安全方面的問
題了。
126. 多線程同步有哪幾種方法?
Synchronized 關鍵字, Lock 鎖實現(xiàn),分布式鎖等 。
127. 線程的調度策略
線程調度器選擇優(yōu)先級最高的線程運行,但是,如果發(fā)生以下情況,就會終止線程的運行:
1、線程體中調用了 yield 方法讓出了對 cpu 的占用權利
2、線程體中調用了 sleep 方法使線程進入睡眠狀態(tài)
3、線程由于 IO 操作受到阻塞
4、另外一個更高優(yōu)先級線程出現(xiàn)
5、在支持時間片的系統(tǒng)中,該線程的時間片用完
128. ConcurrentHashMap 的并發(fā)度是什么
ConcurrentHashMap 的并發(fā)度就是 segment 的大小,默認為 16,這意味著最多同時可以有
16 條線程操作 ConcurrentHashMap,這也是 ConcurrentHashMap 對 Hashtable 的最大優(yōu)勢,
任何情況下, Hashtable 能同時有兩條線程獲取 Hashtable 中的數(shù)據(jù)
129. Linux 用 環(huán)境下如何查找哪個線程使用 CPU 最長
1、獲取項目的 pid, jps 或者 ps -ef | grep java,這個前面有講過
2、 top -H -p pid,順序不能改變
130. Java 死鎖以及如何避免?
Java 中的死鎖是一種編程情況,其中兩個或多個線程被永久阻塞,Java 死鎖情況出現(xiàn)至少
兩個線程和兩個或更多資源。
Java 發(fā)生死鎖的根本原因是:在申請鎖時發(fā)生了交叉閉環(huán)申請 。
131. 死鎖的原因
1、是多個線程涉及到多個鎖,這些鎖存在著交叉,所以可能會導致了一個鎖依賴的閉環(huán)。
例如:線程在獲得了鎖 A 并且沒有釋放的情況下去申請鎖 B,這時,另一個線程已經(jīng)獲得
了鎖 B,在釋放鎖 B 之前又要先獲得鎖 A,因此閉環(huán)發(fā)生,陷入死鎖循環(huán)。
2、默認的鎖申請操作是阻塞的。
所以要避免死鎖,就要在一遇到多個對象鎖交叉的情況,就要仔細審查這幾個對象的類中的
所有方法,是否存在著導致鎖依賴的環(huán)路的可能性??傊潜M量避免在一個同步方法中調用
其它對象的延時方法和同步方法。
132. 怎么喚醒一個阻塞的線程
如果線程是因為調用了 wait()、sleep()或者 join()方法而導致的阻塞,可以中斷線程,并且
通過拋出 InterruptedException 來喚醒它;如果線程遇到了 IO 阻塞,無能為力,因為 IO 是
操作系統(tǒng)實現(xiàn)的,Java 代碼并沒有辦法直接接觸到操作系統(tǒng)。
133. 什么是多線程的上下文切換
多線程的上下文切換是指 CPU 控制權由一個已經(jīng)正在運行的線程切換到另外一個就緒并
等待獲取 CPU 執(zhí)行權的線程的過程。
134. 如果你提交任務時 , 線程池隊列已滿 , 這時會發(fā)生什
么
區(qū)分一下:
1、如果使用的是無界隊列 LinkedBlocking## ueue,也就是無界隊列的話,沒關系,繼續(xù)添加
任務到阻塞隊列中等待執(zhí)行,因為 LinkedBlocking## ueue 可以近乎認為是一個無窮大的隊列,
可以無限存放任務
2 、 如 果 使 用 的 是 有 界 隊 列 比 如 ArrayBlocking## ueue , 任 務 首 先 會 被 添 加 到
ArrayBlocking## ueue 中, ArrayBlocking## ueue 滿了,會根據(jù) maximumPoolSize 的值增加線
程數(shù)量,如果增加了線程數(shù)量還是處理不過來, ArrayBlocking## ueue 繼續(xù)滿,那么則會使
用拒絕策略 RejectedExecutionHandler 處理滿了的任務,默認是 AbortPolicy
135. Java 中用到的線程調度算法是什么
搶占式。一個線程用完 CPU 之后,操作系統(tǒng)會根據(jù)線程優(yōu)先級、線程饑餓情況等數(shù)據(jù)算出
一個總的優(yōu)先級并分配下一個時間片給某個線程執(zhí)行。
136. 什么是自旋
很多 synchronized 里面的代碼只是一些很簡單的代碼,執(zhí)行時間非???,此時等待的線程
都加鎖可能是一種不太值得的操作,因為線程阻塞涉及到用戶態(tài)和內核態(tài)切換的問題。既 然
synchronized 里面的代碼執(zhí)行得非???,不妨讓等待鎖的線程不要被阻塞,而是在
synchronized 的邊界做忙循環(huán),這就是自旋。如 果做了多次忙循環(huán)發(fā)現(xiàn)還沒有獲得鎖,再
阻塞,這樣可能是一種更好的策略。
137. Java Concurrency API 的 中的 Lock 接口(Lock interface)
是什么?對比同步它有什么優(yōu)勢?
Lock 接口比同步方法和同步塊提供了更具擴展性的鎖操作 。 他們允許更靈活的結構,可
以具有完全不同的性質,并且可以支持多個相關類的條件對象。它的優(yōu)勢有:
1、可以使鎖更公平
2、可以使線程在等待鎖的時候響應中斷
3、可以讓線程嘗試獲取鎖,并在無法獲取鎖的時候立即返回或者等待一段時間 4、可以在
不同的范圍,以不同的順序獲取和釋放鎖
138. 單例模式的線程安全性
單例模式的線程安全意味著:某個類的實例在多線程環(huán)境下只會被創(chuàng)建一次出來。單例模式
有很多種的寫法,總結一下:
1、餓漢式單例模式的寫法:線程安全
2、懶漢式單例模式的寫法:非線程安全
3、雙檢鎖單例模式的寫法:線程安全
139. Semaphore 有什么作用
Semaphore 就是一個信號量,它的作用是限制某段代碼塊的并發(fā)數(shù) 。Semaphore 有一個構
造函數(shù),可以傳入一個 int 型整數(shù) n,表 示某段代碼最多只有 n 個線程可以訪問,如果
超出了 n,那 么請等待,等到某個線程執(zhí)行完畢這段代碼塊,下一個線程再進入。 由此可
以看出如果 Semaphore 構造函數(shù)中傳入的 int 型整數(shù) n=1,相當于變成了一個
synchronized 了。
140. 線程類的構造方法、靜態(tài)塊是被哪個線程調用的
線 程 類的構造方法、靜態(tài)塊是被 new 這個線程類所在的線程所調用的,而 run 方 法 里
面的代碼才是被線程自身所調用的。
如果說上面的說法讓你感到困惑,那么我舉個例子,假設 Thread2 中 new 了
Thread1, main 函數(shù)中 new 了 Thread2,那么:
1、 Thread2 的構造方法、靜態(tài)塊是 main 線程調用的, Thread2 的 run()方法是 Thread2
自己調用的
2、 Thread1 的構造方法、靜態(tài)塊是 Thread2 調用的, Thread1 的 run()方法是 Thread1 自
己調用的
141. 同步方法和同步塊,哪個是更好的選擇?
同步塊,這意味著同步塊之外的代碼是異步執(zhí)行的,這比同步整個方法更提升代碼的效率。
請知道一條原則:同步的范圍越小越好。文章來源:http://www.zghlxwxcb.cn/news/detail-839191.html
142. Java 線程數(shù)過多會造成什么異常?
1、線程的生命周期開銷非常高
2、消耗過多的 CPU 資源
如果可運行的線程數(shù)量多于可用處理器的數(shù)量,那么有線程將會被閑置。大量空閑的線程會占用許多內存,給垃圾回收器帶來壓力,而且大量的線程在競爭 CPU 資源時還將產(chǎn)生其他性能的開銷。
3、降低穩(wěn)定性
JVM 在可創(chuàng)建線程的數(shù)量上存在一個限制,這個限制值將隨著平臺的不同而不同,并且承
受著多個因素制約,包括 JVM 的啟動參數(shù)、 Thread 構造函數(shù)中請求棧的大小,以及底層操作系統(tǒng)對線程的限制等。如果破壞了這些限制,那么可能拋出 OutOfMemoryError 異常 。
小白路漫漫,讓我們一起加油文章來源地址http://www.zghlxwxcb.cn/news/detail-839191.html
到了這里,關于JAVA的多線程及并發(fā)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!