繼承Thread類;
實現Runnable接口;
實現Callable接口經過FutureTask包裝器來建立Thread線程;
使用ExecutorService、Callable、Future實現有返回結果的多線程(也就是使用了ExecutorService來管理前面的三種方式)。 java
Thread 類本質上是實現了 Runnable 接口的一個實例,表明一個線程的實例。 啓動線程的惟一方法就是經過 Thread 類的 start()實例方法。 start()方法是一個 native 方法,它將啓動一個新線程,並執行 run()方法。 面試
若是本身的類已經 extends 另外一個類,就沒法直接 extends Thread,此時,能夠實現一個Runnable 接口。 數據庫
有返回值的任務必須實現 Callable 接口,相似的,無返回值的任務必須 Runnable 接口。執行Callable 任務後,能夠獲取一個 Future 的對象,在該對象上調用 get 就能夠獲取到 Callable 任務返回的 Object 了,再結合線程池接口 ExecutorService 就能夠實現傳說中有返回結果的多線程了。 緩存
線程和數據庫鏈接這些資源都是很是寶貴的資源。那麼每次須要的時候建立,不須要的時候銷燬,是很是浪費資源的。那麼咱們就可使用緩存的策略,也就是使用線程池。 安全
Java 裏面線程池的頂級接口是 Executor,可是嚴格意義上講 Executor 並非一個線程池,而只是一個執行線程的工具。真正的線程池接口是 ExecutorService。 多線程
newCachedThreadPool
建立一個可根據須要建立新線程的線程池,可是在之前構造的線程可用時將重用它們。對於執行不少短時間異步任務的程序而言,這些線程池一般可提升程序性能。 調用 execute 將重用之前構造的線程(若是線程可用)。若是現有線程沒有可用的,則建立一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。 所以,長時間保持空閒的線程池不會使用任何資源。
newFixedThreadPool
建立一個可重用固定線程數的線程池,以共享的***隊列方式來運行這些線程。在任意點,在大多數 nThreads 線程會處於處理任務的活動狀態。若是在全部線程處於活動狀態時提交附加任務,則在有可用線程以前,附加任務將在隊列中等待。若是在關閉前的執行期間因爲失敗而致使任何線程終止,那麼一個新線程將代替它執行後續的任務(若是須要)。在某個線程被顯式地關閉以前,池中的線程將一直存在。
newScheduledThreadPool
建立一個線程池,它可安排在給定延遲後運行命令或者按期地執行。
newSingleThreadExecutor
Executors.newSingleThreadExecutor()返回一個線程池(這個線程池只有一個線程) ,這個線程池能夠在線程死後(或發生異常時)從新啓動一個線程來替代原來的線程繼續執行下去! 併發
1.使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。
2.使用stop方法強行終止,可是不推薦這個方法,由於stop和suspend及resume同樣都是過時做廢的方法。
3.使用interrupt方法中斷線程。異步
notify可能會致使死鎖,而notifyAll則不會
任什麼時候候只有一個線程能夠得到鎖,也就是說只有一個線程能夠運行synchronized 中的代碼使用notifyall,能夠喚醒全部處於wait狀態的線程,使其從新進入鎖的爭奪隊列中,而notify只能喚醒一個。
wait() 應配合while循環使用,不該使用if,務必在wait()調用先後都檢查條件,若是不知足,必須調用notify()喚醒另外的線程來處理,本身繼續wait()直至條件知足再往下執行。
notify() 是對notifyAll()的一個優化,但它有很精確的應用場景,而且要求正確使用。否則可能致使死鎖。正確的場景應該是 WaitSet中等待的是相同的條件,喚醒任一個都能正確處理接下來的事項,若是喚醒的線程沒法正確處理,務必確保繼續notify()下一個線程,而且自身須要從新回到WaitSet中. jvm
一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾以後,那麼就具有了兩層語義:
1)保證了不一樣線程對這個變量進行操做時的可見性,即一個線程修改了某個變量的值,這新值對其餘線程來講是當即可見的,volatile關鍵字會強制將修改的值當即寫入主存。
2)禁止進行指令重排序。
volatile 不是原子性操做
什麼叫保證部分有序性?
當程序執行到volatile變量的讀操做或者寫操做時,在其前面的操做的更改確定所有已經進行,且結果已經對後面的操做可見;在其後面的操做確定尚未進行;
因爲flag變量爲volatile變量,那麼在進行指令重排序的過程的時候,不會將語句3放到語句一、語句2前面,也不會講語句3放到語句四、語句5後面。可是要注意語句1和語句2的順序、語句4和語句5的順序是不做任何保證的。使用 Volatile 通常用於 狀態標記量 和 單例模式的雙檢鎖 ide
start()方法被用來啓動新建立的線程,並且start()內部調用了run()方法,這和直接調用run()方法的效果不同。當你調用run()方法的時候,只會是在原來的線程中調用,沒有新的線程啓動,start()方法纔會啓動新線程 。
明顯的緣由是JAVA提供的鎖是對象級的而不是線程級的,每一個對象都有鎖,經過線程得到。若是線程須要等待某些鎖那麼調用對象中的wait()方法就有意義了。若是wait()方法定義在Thread類中,線程正在等待的是哪一個鎖就不明顯了。簡單的說,因爲wait,notify和notifyAll都是鎖級別的操做,因此把他們定義在Object類中由於鎖屬於對象 。
interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除然後者不會。Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識爲true。
當中斷線程調用靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。
而非靜態方法isInterrupted()用來查詢其它線程的中斷狀態且不會改變中斷狀態標識。簡單的說就是任何拋出InterruptedException異常的方法都會將中斷狀態清零。不管如何,一個線程的中斷狀態有有可能被其它線程調用中斷來改變 。
類似點:
這兩種同步方式有不少類似之處,它們都是加鎖方式同步,並且都是阻塞式的同步,也就是說當若是一個線程得到了對象鎖,進入了同步塊,其餘訪問該同步塊的線程都必須阻塞在同步塊外面等待,而進行線程阻塞和喚醒的代價是比較高的.
區別:
這兩種方式最大區別就是對於Synchronized來講,它是java語言的關鍵字,是原生語法層面的互斥,須要jvm實現。而ReentrantLock它是JDK 1.5以後提供的API層面的互斥鎖,須要lock()和unlock()方法配合try/finally語句塊來完成。
Synchronized進過編譯,會在同步塊的先後分別造成monitorenter和monitorexit這個兩個字節碼指令。在執行monitorenter指令時,首先要嘗試獲取對象鎖。若是這個對象沒被鎖定,或者當前線程已經擁有了那個對象鎖,把鎖的計算器加1,相應的,在執行monitorexit指令時會將鎖計算器就減1,當計算器爲0時,鎖就被釋放了。若是獲取對象鎖失敗,那當前線程就要阻塞,直到對象鎖被另外一個線程釋放爲止 。
因爲ReentrantLock是java.util.concurrent包下提供的一套互斥鎖,相比Synchronized,ReentrantLock類提供了一些高級功能,主要有如下3項:
1.等待可中斷,持有鎖的線程長期不釋放的時候,正在等待的線程能夠選擇放棄等待,這至關於Synchronized來講能夠避免出現死鎖的狀況。
2.公平鎖,多個線程等待同一個鎖時,必須按照申請鎖的時間順序得到鎖,Synchronized鎖非公平鎖,ReentrantLock默認的構造函數是建立的非公平鎖,能夠經過參數true設爲公平鎖,但公平鎖表現的性能不是很好。
3.鎖綁定多個條件,一個ReentrantLock對象能夠同時綁定對個對象 。
在多線程中有多種方法讓線程按特定順序執行,你能夠用線程類的join()方法在一個線程中啓動另外一個線程,另一個線程完成該線程繼續執行。爲了確保三個線程的順序你應該先啓動最後一個(T3調用T2,T2調用T1),這樣T1就會先完成而T3最後完成。
實際上先啓動三個線程中哪個都行,由於在每一個線程的run方法中用join方法限定了三個線程的執行順序 。
SynchronizedMap()和Hashtable同樣,實現上在調用map全部方法時,都對整個map進行同步。而ConcurrentHashMap的實現卻更加精細,它對map中的全部桶加了鎖。因此,只要有一個線程訪問map,其餘線程就沒法進入map,而若是一個線程在訪問ConcurrentHashMap某個桶時,其餘線程,仍然能夠對map執行某些操做。因此,ConcurrentHashMap在性能以及安全性方面,明顯比Collections.synchronizedMap()更加有優點。同時,同步操做精確控制到桶,這樣,即便在遍歷map時,若是其餘線程試圖對map進行數據修改,也不會拋出ConcurrentModificationException 。
線程安全就是說多線程訪問同一代碼,不會產生不肯定的結果。
在多線程環境中,當各線程不共享數據的時候,即都是私有(private)成員,那麼必定是線程安全的。
但這種狀況並很少見,在多數狀況下須要共享數據,這時就須要進行適當的同步控制了。
線程安全通常都涉及到synchronized, 就是一段代碼同時只能有一個線程來操做 否則中間過程可能會產生不可預製的結果。
若是你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。若是每次運行結果和單線程運行的結果是同樣的,並且其餘的變量的值也和預期的是同樣的,就是線程安全的。
Yield方法能夠暫停當前正在執行的線程對象,讓其它有相同優先級的線程執行。它是一個靜態方法並且只保證當前線程放棄CPU佔用而不能保證使其它線程必定能佔用CPU,執行yield()的線程有可能在進入到暫停狀態後立刻又被執行。
兩個方法均可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法能夠返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和
ScheduledThreadPoolExecutor都有這些方法 。
更多面試題,歡迎關注公衆號【慕容千語】