ReadMe : 括號裏的內容爲補充或解釋說明。html
多線程和高併發是畢業後求職大廠面試中必問的知識點,本身以前老是面試前纔去找相關的知識點面試題來背背,隔段時間又忘了,沒有沉澱下來,因而本身總結了下相關的知識點。面試
進程是一個獨立的運行環境,它能夠被看做是一個程序或者一個應用。而線程是在進程中執行的一個任務。進程是操做系統進行資源分配的基本單位,而線程是操做系統進行調度的基本單位。進程讓操做系統的併發性成爲可能,而線程讓進程的內部併發成爲可能。比如Java運行環境是一個(包含了不一樣的類和程序的)單一進程。緩存
想理解的更深入,請點擊進程與線程的區別.安全
1) start()被用來啓動新的線程,run()不能。多線程
2)start()不能被重複調用,run()能夠。併發
3)start()中的run代碼能夠不執行完就繼續執行下面的代碼,即線程轉換,若是直接調用run()必須等待其代碼所有執行完才能繼續執行下面的代碼。異步
4)start()實現了多線程,run()沒有實現多線程。ide
上下文切換是存儲和恢復CPU狀態的過程,它使得線程執行可以從中斷點恢復執行。是多任務操做系統和多線程環境的基本特徵。函數
volatile是一個特殊的修飾符,只有成員變量(類的成員變量、類的靜態成員變量)才能使用它。高併發
被volatile修飾以後就具有了兩層語義:
1)保證了不一樣線程對這個變量進行操做時的可見性(即一個線程修改了某個變量的值,這新值對其餘線程來講是即刻可見的)。
2)禁止進行指令重排序。
棧是一塊和線程緊密相關的內存區域。每一個線程都有本身的棧內存,用於存儲本地變量,方法參數和棧調用,一個線程中存儲的變量對其它線程是不可見的。
堆是全部線程共享的一片公用內存區域。對象都在堆裏建立,爲了提高效率線程會從堆中弄一個緩存到本身的棧,若是多個線程使用該變量就可能引起問題,這時volatile 變量就能夠發揮做用了,它要求線程從主存中讀取變量的值。
建立線程要花費資源和時間,若是任務來了才建立線程那麼響應時間會變長,並且一個進程能建立的線程數有限。爲了不這些問題,在程序啓動的時候就建立若干線程來響應處理,它們被稱爲線程池,裏面的線程叫工做線程。
死鎖是指兩個或兩個以上的進程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。這是一個嚴重的問題,由於死鎖會讓你的程序掛起沒法完成任務,死鎖的發生必須知足如下四個條件:
1)互斥條件:一個資源每次只能被一個進程使用。
2)請求與保持條件:一個進程因請求資源而阻塞時,對已得到的資源保持不放。
3)不剝奪條件:進程已得到的資源,在末使用完以前,不能強行剝奪。
4)循環等待條件:若干進程之間造成一種頭尾相接的循環等待資源關係。
避免死鎖最簡單的方法就是阻止循環等待條件,將系統中全部的資源設置標誌位、排序,規定全部的進程申請資源必須以必定的順序(升序或降序)作操做來避免死鎖。
Thread.yield() 方法會使當前線程從運行狀態變爲就緒狀態,把運行機會讓給其它相同優先級的線程。它是一個靜態的原生(native)方法並且只保證當前線程放棄CPU佔用而不能保證使其它線程必定能佔用CPU,執行yield()的線程有可能會被再次繼續執行的。
調用notify時,只有一個等待線程會被喚醒並且它不能保證哪一個線程會被喚醒,這取決於線程調度器。雖然若是你調用notifyAll方法,那麼等待該鎖的全部線程都會被喚醒。
interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除然後者不會。
Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識爲true。當中斷線程調用靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。而非靜態方法isInterrupted()用來查詢其它線程的中斷狀態且不會改變中斷狀態標識。簡單的說就是任何拋出InterruptedException異常的方法都會將中斷狀態清零。不管如何,一個線程的中斷狀態有有可能被其它線程調用中斷來改變。
sleep()和wait()都是使線程暫停執行一段時間的方法。兩者區別爲:
1)原理不一樣。
sleep()方法是Thread類的靜態方法,是線程用來控制自身流程的,它會使此線程暫停執行一段時間,而把執行機會讓給其餘線程,等到計時時間一到,此線程會自動甦醒。而wait()方法是Object類的方法,用於線程間的通訊,這個方法會使當前擁有該對象鎖的進程等待,直到其餘線程用調用notify()或notifyAll()時才甦醒過來,開發人員也能夠給它指定一個時間使其自動醒來。
2)對鎖的處理機制不一樣。
因爲sleep()方法的主要做用是讓線程暫停一段時間,時間一到則自動恢復,不涉及線程間的通訊,所以調用sleep()方法僅僅釋放CPU資源或者讓當前線程中止執行一段時間,但不會釋放鎖。而wait()方法則不一樣,當調用wait()方法後,線程會釋放掉它所佔用的鎖,從而使線程所在對象中的其餘synchronized數據可被別的線程使用。
3)使用區域不一樣。
wait()方法必須放在同步控制方法或者同步語句塊中使用,而sleep方法則能夠放在任何地方使用。sleep()方法必須捕獲異常,而wait()、notify()、notifyAll()不須要捕獲異常。在sleep的過程當中,有可能被其餘對象調用它的interrupt(),產生InterruptedException異常。
因爲sleep不會釋放鎖標誌,容易致使死鎖問題的發生,通常狀況下,不推薦使用sleep()方法,而推薦使用wait()方法。
在多線程中有多種方法讓線程按特定順序執行,你能夠用線程類的join()方法在一個線程中啓動另外一個線程,另一個線程完成該線程繼續執行。爲了確保三個線程的順序你應該先啓動最後一個(T3調用T2,T2調用T1),這樣T1就會先完成而T3最後完成。
使用Thread類的setDaemon(true)方法能夠將線程設置爲守護線程,須要注意的是,須要在調用start()方法前調用這個方法,不然會拋出IllegalThreadStateException異常。
/** * @author liao.wenhui * @date 2019/7/15 15:13 */ public class DaemonThread { public static void main(String[] args) { Thread daemonThread = new Thread(new Runnable() { @Override public void run() { } }); //設置守護線程 daemonThread.setDaemon(true); daemonThread.start(); } }
前提知識:
守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端而且週期性地執行某種任務或等待處理某些發生的事件(百度百科)。
Java線程分爲兩類分別爲daemon線程(守護線程)和User線程(用戶線程),在JVM啓動時候會調用main函數,main函數所在的線程是一個用戶線程,這個是咱們能夠看到的線程,其實JVM內部同時還啓動了好多守護線程,好比垃圾回收線程。那麼守護線程和用戶線程有什麼區別那?區別之一是當最後一個非守護線程結束時候,JVM會正常退出,而無論當前是否有守護線程,也就是說守護線程是否結束並不影響JVM的退出。言外之意是隻要有一個用戶線程還沒結束正常狀況下JVM就不會退出。
線程調度器是一個操做系統服務,它負責爲Runnable狀態的線程分配CPU時間。一旦咱們建立一個線程並啓動它,它的執行便依賴於線程調度器的實現。
時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。分配CPU時間能夠基於線程優先級或者線程等待的時間。線程調度並不受到Java虛擬機控制,因此由應用程序來控制它是更好的選擇(即最好不要讓你的程序依賴於線程的優先級)。
ThreadLocal用於建立線程的本地變量,咱們知道一個對象的全部線程會共享它的全局變量,因此這些變量不是線程安全的,咱們可使用同步技術。可是當咱們不想使用同步的時候,咱們能夠選擇ThreadLocal變量。每一個線程都會擁有他們本身的Thread變量,它們可使用get()/set()方法去獲取他們的默認值或者在線程內部改變他們的值。
兩個方法均可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法能夠返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。
Runnable和Callable都表明那些要在不一樣的線程中執行的任務。Runnable從JDK1.0開始就有了,Callable是在JDK1.5增長的。它們的主要區別是Callable的 call() 方法能夠返回任務執行結果值和拋出異常,而Runnable的run()方法沒有這些功能。Callable能夠返回裝載有計算結果的Future對象。
在Java併發程序中FutureTask表示一個能夠取消的異步運算。它有啓動和取消運算、查詢運算是否完成和取回運算結果等方法。只有當運算完成的時候結果才能取回,若是運算還沒有完成get方法將會阻塞。一個FutureTask對象能夠對調用了Callable和Runnable的對象進行包裝,因爲FutureTask也是調用了Runnable接口因此它能夠提交給Executor來執行。