Java线程问题问答
1、多線程的作用?
2:平時項目中使用鎖和synchronized比較多,而很少使用volatile,難道就沒有保證可見性?
鎖和synchronized即可以保證原子性,也可以保證可見性。都是通過保證同一時間只有一個線程執行目標代碼段來實現的。
3:鎖和synchronized為何能保證可見性?
根據JDK 7的Java
doc中對concurrent包的說明,一個線程的寫結果保證對另外線程的讀操作可見,只要該寫操作可以由happen-before原則推斷出在讀操作之前發生。
4:既然鎖和synchronized即可保證原子性也可保證可見性,為何還需要volatile?
synchronized和鎖需要通過操作系統來仲裁誰獲得鎖,開銷比較高,而volatile開銷小很多。因此在只需要保證可見性的條件下,使用volatile的性能要比使用鎖和synchronized高得多。
5:既然鎖和synchronized可以保證原子性,為什么還需要AtomicInteger這種的類來保證原子操作?
鎖和synchronized需要通過操作系統來仲裁誰獲得鎖,開銷比較高,而AtomicInteger是通過CPU級的CAS操作來保證原子性,開銷比較小。所以使用AtomicInteger的目的還是為了提高性能。
6:還有沒有別的辦法保證線程安全
有。盡可能避免引起非線程安全的條件——共享變量。如果能從設計上避免共享變量的使用,即可避免非線程安全的發生,也就無須通過鎖或者synchronized以及volatile解決原子性、可見性和順序性的問題。
7:synchronized即可修飾非靜態方式,也可修飾靜態方法,還可修飾代碼塊,有何區別
synchronized修飾非靜態同步方法時,鎖住的是當前實例;synchronized修飾靜態同步方法時,鎖住的是該類的Class對象;synchronized修飾靜態代碼塊時,鎖住的是synchronized關鍵字后面括號內的對象。
8、CyclicBarrier和CountDownLatch的區別
兩個看上去有點像的類,都在java.util.concurrent下,都可以用來表示代碼運行到某個點上,二者的區別在于:
9、Java中如何獲取到線程dump文件
死循環、死鎖、阻塞、頁面打開慢等問題,打線程dump是最好的解決問題的途徑。所謂線程dump也就是線程堆棧,獲取到線程堆棧有兩步:
另外提一點,Thread類提供了一個getStackTrace()方法也可以用于獲取線程堆棧。這是一個實例方法,因此此方法是和具體線程實例綁定的,每次獲取獲取到的是具體某個線程當前運行的堆棧
10、一個線程如果出現了運行時異常會怎么樣
如果這個異常沒有被捕獲的話,這個線程就停止執行了。另外重要的一點是:如果這個線程持有某個某個對象的監視器,那么這個對象監視器會被立即釋放
11、如何在兩個線程之間共享數據
通過在線程之間共享對象就可以了,然后通過wait/notify/notifyAll、await/signal/signalAll進行喚起和等待,比方說阻塞隊列BlockingQueue就是為線程之間共享數據而設計的
12、sleep方法和wait方法有什么區別
這個問題常問,sleep方法和wait方法都可以用來放棄CPU一定的時間,不同點在于如果線程持有某個對象的監視器,sleep方法不會放棄這個對象的監視器,wait方法會放棄這個對象的監視器
13、生產者消費者模型的作用是什么
這個問題很理論,但是很重要:
14、ThreadLocal有什么用
簡單說ThreadLocal就是一種以空間換時間的做法,在每個Thread里面維護了一個以開地址法實現的ThreadLocal.ThreadLocalMap,把數據進行隔離,數據不共享,自然就沒有線程安全方面的問題了
15、為什么wait()方法和notify()/notifyAll()方法要在同步塊中被調用
這是JDK強制的,wait()方法和notify()/notifyAll()方法在調用前都必須先獲得對象的鎖
16、wait()方法和notify()/notifyAll()方法在放棄對象監視器時有什么區別
wait()方法和notify()/notifyAll()方法在放棄對象監視器的時候的區別在于:wait()方法立即釋放對象監視器,notify()/notifyAll()方法則會等待線程剩余代碼執行完畢才會放棄對象監視器。
17、為什么要使用線程池
避免頻繁地創建和銷毀線程,達到線程對象的重用。另外,使用線程池還可以根據項目靈活地控制并發的數目。
18、怎么檢測一個線程是否持有對象監視器
我也是在網上看到一道多線程面試題才知道有方法可以判斷某個線程是否持有對象監視器:Thread類提供了一個holdsLock(Object
obj)方法,當且僅當對象obj的監視器被某條線程持有的時候才會返回true,注意這是一個static方法,這意味著“某條線程”指的是當前線程。
19、synchronized和ReentrantLock的區別
synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。既然ReentrantLock是類,那么它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上:
另外,二者的鎖機制其實也是不一樣的。ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark
word,這點我不能確定。
20、ConcurrentHashMap的并發度是什么
ConcurrentHashMap的并發度就是segment的大小,默認為16,這意味著最多同時可以有16條線程操作ConcurrentHashMap,這也是ConcurrentHashMap對Hashtable的最大優勢,任何情況下,Hashtable能同時有兩條線程獲取Hashtable中的數據嗎?
21、ReadWriteLock是什么
首先明確一下,不是說ReentrantLock不好,只是ReentrantLock某些時候有局限。如果使用ReentrantLock,可能本身是為了防止線程A在寫數據、線程B在讀數據造成的數據不一致,但這樣,如果線程C在讀數據、線程D也在讀數據,讀數據是不會改變數據的,沒有必要加鎖,但是還是加鎖了,降低了程序的性能。
因為這個,才誕生了讀寫鎖ReadWriteLock。ReadWriteLock是一個讀寫鎖接口,ReentrantReadWriteLock是ReadWriteLock接口的一個具體實現,實現了讀寫的分離,讀鎖是共享的,寫鎖是獨占的,讀和讀之間不會互斥,讀和寫、寫和讀、寫和寫之間才會互斥,提升了讀寫的性能。
22、FutureTask是什么
這個其實前面有提到過,FutureTask表示一個異步運算的任務。FutureTask里面可以傳入一個Callable的具體實現類,可以對這個異步運算的任務的結果進行等待獲取、判斷是否已經完成、取消任務等操作。當然,由于FutureTask也是Runnable接口的實現類,所以FutureTask也可以放入線程池中。
23、Linux環境下如何查找哪個線程使用CPU最長
這是一個比較偏實踐的問題,這種問題我覺得挺有意義的。可以這么做:
使用”top -H -p pid”+”jps
pid”可以很容易地找到某條占用CPU高的線程的線程堆棧,從而定位占用CPU高的原因,一般是因為不當的代碼操作導致了死循環。
最后提一點,”top -H -p pid”打出來的LWP是十進制的,”jps
pid”打出來的本地線程號是十六進制的,轉換一下,就能定位到占用CPU高的線程的當前線程堆棧了。
24、怎么喚醒一個阻塞的線程
如果線程是因為調用了wait()、sleep()或者join()方法而導致的阻塞,可以中斷線程,并且通過拋出InterruptedException來喚醒它;如果線程遇到了IO阻塞,無能為力,因為IO是操作系統實現的,Java代碼并沒有辦法直接接觸到操作系統。
25、Thread.sleep(0)的作用是什么
這個問題和上面那個問題是相關的,我就連在一起了。由于Java采用搶占式的線程調度算法,因此可能會出現某條線程常常獲取到CPU控制權的情況,為了讓某些優先級比較低的線程也能獲取到CPU控制權,可以使用Thread.sleep(0)手動觸發一次操作系統分配時間片的操作,這也是平衡CPU控制權的一種操作。
26、什么是自旋
很多synchronized里面的代碼只是一些很簡單的代碼,執行時間非常快,此時等待的線程都加鎖可能是一種不太值得的操作,因為線程阻塞涉及到用戶態和內核態切換的問題。既然synchronized里面的代碼執行地非常快,不妨讓等待鎖的線程不要被阻塞,而是在synchronized的邊界做忙循環,這就是自旋。如果做了多次忙循環發現還沒有獲得鎖,再阻塞,這樣可能是一種更好的策略。
27、什么是AQS
簡單說一下AQS,AQS全稱為AbstractQueuedSychronizer,翻譯過來應該是抽象隊列同步器。
如果說java.util.concurrent的基礎是CAS的話,那么AQS就是整個Java并發包的核心了,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS實際上以雙向隊列的形式連接所有的Entry,比方說ReentrantLock,所有等待的線程都被放在一個Entry中并連成雙向隊列,前面一個線程使用ReentrantLock好了,則雙向隊列實際上的第一個Entry開始運行。
AQS定義了對雙向隊列所有的操作,而只開放了tryLock和tryRelease方法給開發者使用,開發者可以根據自己的實現重寫tryLock和tryRelease方法,以實現自己的并發功能。
28、Semaphore有什么作用
Semaphore就是一個信號量,它的作用是限制某段代碼塊的并發數。Semaphore有一個構造函數,可以傳入一個int型整數n,表示某段代碼最多只有n個線程可以訪問,如果超出了n,那么請等待,等到某個線程執行完畢這段代碼塊,下一個線程再進入。由此可以看出如果Semaphore構造函數中傳入的int型整數n=1,相當于變成了一個synchronized了。
29、為什么wait、notify定義在Object類里,而不是Thread類里?
因為wait、notify依賴于對象鎖(monitor),而對象鎖是每個對象所擁有的,而不是線程獨有的。因此,定義在Object里
總結
以上是生活随笔為你收集整理的Java线程问题问答的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JDK线程池实现
- 下一篇: Federation 简介