Java基礎篇——線程、併發編程知識點全面介紹(面試、學習的必備索引)

原創不易,如需轉載,請註明出處http://www.javashuo.com/article/p-xepdqbaa-hy.html,但願你們多多支持!!! html

1、線程基礎

一、線程與進程

  • 線程是指進程中的一個執行流程,一個進程中能夠運行多個線程。
  • 進程是指一個內存中運行的應用程序,每一個進程都有本身獨立的一塊內存空間,即進程空間或(虛空間),好比一個qq.exe就是一個進程。

二、線程的特色

  • 線程共享分配給該進程的全部資源
  • 線程之間實際上輪換執行(也就是線程切換)
  • 一個程序至少有一個進程,一個進程至少有一個線程
  • 線程不可以獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制
  • 線程有本身的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程包含如下內容:
    • 一個指向當前被執行指令的指令指針
    • 一個棧
    • 一個寄存器值的集合,定義了一部分描述正在執行線程的處理器狀態的值
    • 一個私有的數據區

三、線程的做用

  • 進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存,從而極大地提升了程序的運行效率(併發執行)

四、線程的建立

  • 繼承Thread類
  • 實現Runnable接口
  • 經過ThreadPool獲取

五、線程的狀態(生命週期)

  • 建立:當用new操做符建立一個線程時。此時程序尚未開始運行線程中的代碼
  • 就緒:當start()方法返回後,線程就處於就緒狀態
  • 運行:當線程得到CPU時間後,它才進入運行狀態,真正開始執行run()方法
  • 阻塞:所謂阻塞狀態是正在運行的線程沒有運行結束,暫時讓出CPU,這時其餘處於就緒狀態的線程就能夠得到CPU時間,進入運行狀態
  • 死亡:一、run方法正常退出而天然死亡;二、一個未捕獲的異常終止了run方法而使線程猝死java

    線程生命週期.jpg

六、線程的優先級、線程讓步yield、線程合併join、線程睡眠sleep

  • 優先級:線程老是存在優先級,優先級範圍在1~10之間(數值越大優先級越高),線程默認優先級是5,優先級高的理論上先執行,但實際不必定。
  • 線程讓步:yield方法調用後 ,是直接進入就緒狀態,因此有可能剛進入就緒狀態,又被調度到運行狀態(使用yield()的目的是讓相同優先級的線程之間能適當的輪轉執行)。
  • 線程合併:保證當前線程中止執行,直到該線程所加入的線程完成爲止。然而,若是它加入的線程沒有存活,則當前線程不須要中止。
  • 線程睡眠:sleep方法暫停當前線程後,會進入阻塞狀態,只有當睡眠時間到了,纔會轉入就緒狀態。

七、線程的分類(如下二者的惟一區別之處就在虛擬機的離開)

  • 守護線程: thread.setDaemon(true),必須在thread.start()以前設置,GC線程就是一個守護線程。
  • 普通線程

八、正確結束線程(給出一些方案)

  • 使用Thread.stop()方法,可是該方法已經被廢棄了,使用它是極端不安全的,會形成數據不一致的問題。
  • 使用interrupt()方法中止一個線程,直接調用該方法不會終止一個正在運行的線程,須要加入一個判斷語句才能夠完成線程的中止。
  • 使用共享變量的方式,在這種方式中,之因此引入共享變量,是由於該變量能夠被多個執行相同任務的線程用來做爲是否中斷的信號,通知中斷線程的執行。

2、線程同步

一、線程同步的意義

  • 線程的同步是爲了防止多個線程訪問一個數據對象時,對數據形成的破壞

二、鎖的原理

  • Java中每一個對象都有一個內置鎖,內置鎖是一個互斥鎖,這就是意味着最多隻有一個線程可以得到該鎖。
  • 當程序運行到非靜態的synchronized同步方法上時,自動得到與正在執行代碼類的當前實例(this實例)有關的鎖。得到一個對象的鎖也稱爲獲取鎖、鎖定對象、在對象上鎖定或在對象上同步。
  • 當程序運行到synchronized同步方法或代碼塊時才該對象鎖才起做用。
  • 一個對象只有一個鎖。因此,若是一個線程得到該鎖,就沒有其餘線程能夠得到鎖,直到第一個線程釋放(或返回)鎖。這也意味着任何其餘線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。
  • 釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。

三、鎖和同步的理解

  • 只能同步方法,而不能同步變量和類。
  • 每一個對象只有一個鎖,當提到同步時,應該清楚在什麼上同步,也就是說,在哪一個對象上同步。
  • 沒必要同步類中全部的方法,類能夠同時擁有同步和非同步方法。
  • 若是兩個線程要執行一個類中的synchronized方法,而且兩個線程使用相同的實例來調用方法,那麼一次只能有一個線程可以執行方法,另外一個須要等待,直到鎖被釋放。也就是說:若是一個線程在對象上得到一個鎖,就沒有任何其餘線程能夠進入(該對象的)類中的任何一個同步方法
  • 若是線程擁有同步和非同步方法,則非同步方法能夠被多個線程自由訪問而不受鎖的限制。
  • 線程睡眠時,它所持的任何鎖都不會釋放。
  • 線程能夠得到多個鎖。好比,在一個對象的同步方法裏面調用另一個對象的同步方法,則獲取了兩個對象的同步鎖。
  • 同步損害併發性,應該儘量縮小同步範圍。同步不但能夠同步整個方法,還能夠同步方法中一部分代碼塊。
  • 在使用同步代碼塊時候,應該指定在哪一個對象上同步,也就是說要獲取哪一個對象的鎖。

四、對象鎖和類鎖的區別

  • 對象鎖是用於對象實例方法,或者一個對象實例上的,類鎖是用於類的靜態方法或者一個類的class對象上的。
  • 類的對象實例能夠有不少個,可是每一個類只有一個class對象,因此不一樣對象實例的對象鎖是互不干擾的,可是每一個類只有一個類鎖。

五、線程的死鎖及規避

  • 死鎖是線程間相互等待鎖鎖形成的,一旦程序發生死鎖,程序將死掉。
  • 若是咱們可以避免在對象的同步方法中調用其它對象的同步方法,那麼就能夠避免死鎖產生的可能性。

六、volatile關鍵字(推薦你們一片文章)

  • 正確使用 volatile變量
  • 與鎖相比,Volatile 變量是一種很是簡單但同時又很是脆弱的同步機制,它在某些狀況下將提供優於鎖的性能和伸縮性。
  • 若是嚴格遵循 volatile 的使用條件 —— 即變量真正獨立於其餘變量和本身之前的值 —— 在某些狀況下可使用 volatile 代替 synchronized 來簡化代碼。

3、線程的交互

一、線程交互的基礎知識

  • void notify()——喚醒在此對象監視器上等待的單個線程。
  • void notifyAll()——喚醒在此對象監視器上等待的全部線程。
  • void wait()——致使當前的線程等待,直到其餘線程調用此對象的 notify()方法或 notifyAll()方法。
  • void wait(longtimeout)——致使當前的線程等待,直到其餘線程調用此對象的 notify()方法或 notifyAll()方法,或者超過指定的時間量。
  • void wait(longtimeout, int nanos)——致使當前的線程等待,直到其餘線程調用此對象的 notify()方法或 notifyAll()方法,或者其餘某個線程中斷當前線程,或者已超過某個實際時間量。

二、注意點

  • 必須從同步環境內調用wait()、notify()、notifyAll()方法。線程不能調用對象上等待或通知的方法,除非它擁有那個對象的鎖。
  • 當在對象上調用wait()方法時,執行該代碼的線程當即放棄它在對象上的鎖。然而調用notify()時,並不意味着這時線程會放棄其鎖。若是線程仍然在完成同步代碼,則線程在移出以前不會放棄鎖。所以,調用notify()並不意味着這時該鎖變得可用。

4、線程池

一、好處

  • 下降資源消耗。經過重複利用已建立的線程下降線程建立和銷燬形成的消耗。
  • 提升線程的可管理性。線程是稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一的分配,調優和監控。
  • 提升響應速度。當任務到達時,任務能夠不須要等到線程建立就能當即執行。

二、線程池的建立使用

public class ThreadPoolExecutor extends AbstractExecutorService {
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    }
  • 參數介紹:
    • corePoolSize:核心池的大小
    • maximumPoolSize:線程池最大線程數
    • keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止
    • unit:參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性:
      • TimeUnit.DAYS; //天
      • TimeUnit.HOURS; //小時
      • TimeUnit.MINUTES; //分鐘
      • TimeUnit.SECONDS; //秒
      • TimeUnit.MILLISECONDS; //毫秒
      • TimeUnit.MICROSECONDS; //微妙
      • TimeUnit.NANOSECONDS; //納秒
    • workQueue:一個阻塞隊列,用來存儲等待執行的任務
    • threadFactory:線程工廠,主要用來建立線程
    • handler:表示當拒絕處理任務時的策略,有如下四種取值:
      • ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
      • ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,可是不拋出異常。
      • ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,而後從新嘗試執行任務(重複此過程)
      • ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

三、java默認實現的4中線程池(不建議使用)

  • newCachedThreadPool:建立一個可緩存線程池,若是線程池長度超過處理須要,可靈活回收空閒線程,若無可回收,則新建線程。線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。
  • newFixedThreadPool:建立一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()。
  • newScheduledThreadPool:建立一個定長線程池,支持定時及週期性任務執行。
  • newSingleThreadExecutor:建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行。

5、併發編程相關內容

一、synchronized 的侷限性 與 Lock 的優勢

  • 首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
  • synchronized沒法判斷是否獲取鎖的狀態,Lock能夠判斷是否獲取到鎖;
  • synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程當中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),不然容易形成線程死鎖;
  • 用synchronized關鍵字的兩個線程1和線程2,若是當前線程1得到鎖,線程2線程等待。若是線程1阻塞,線程2則會一直等待下去,而Lock鎖就不必定會等待下去,若是嘗試獲取不到鎖,線程能夠不用一直等待就結束了;
  • synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(二者皆可)
  • Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少許的同步問題。

二、Lock 和 ReadWriteLock

  • Lock接口,ReentrantLock(可重入鎖)是惟一的實現類。git

    public interface Lock {
        void lock();
        //lockInterruptibly()方法比較特殊,當經過這個方法去獲取鎖時,若是線程正在等待獲取鎖,則這個線程可以響應中斷,即中斷線程的等待狀態。
        //也就使說,當兩個線程同時經過lock.lockInterruptibly()想獲取某個鎖時,倘若此時線程A獲取到了鎖,而線程B只有在等待,那麼對線程B調用threadB.interrupt()方法可以中斷線程B的等待過程。
        void lockInterruptibly() throws InterruptedException;
        boolean tryLock();
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
        void unlock();
        Condition newCondition();
    }
  • ReadWriteLock接口,ReentrantReadWriteLock實現了ReadWriteLock接口。github

    public interface ReadWriteLock {
        Lock readLock();
        Lock writeLock();
    }
    • 讀讀共享
    • 寫寫互斥
    • 讀寫互斥
    • 寫讀互斥

三、信號量(Semaphore)

  • 介紹:Java的信號量其實是一個功能完畢的計數器,對控制必定資源的消費與回收有着很重要的意義,信號量經常用於多線程的代碼中,並能監控有多少數目的線程等待獲取資源,而且經過信號量能夠得知可用資源的數目等等。
  • 特色:
    • 以控制某個資源可被同時訪問的個數,經過 acquire() 獲取一個許可,若是沒有就等待,而 release() 釋放一個許可。
    • 單個信號量的Semaphore對象能夠實現互斥鎖的功能,而且能夠是由一個線程得到了「鎖」,再由另外一個線程釋放「鎖」,這可應用於死鎖恢復的一些場合。
    • 信號量解決了鎖一次只能讓一個線程訪問資源的問題,信號量能夠指定多個線程,同時訪問一個資源。
  • 分爲公平模式和非公平模式(默認非公平)編程

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

    區別在於:公平模式會考慮是否已經有線程在等待,若是有則直接返回-1表示獲取失敗;而非公平模式不會關心有沒有線程在等待,會去快速競爭資源的使用權。
    說到競爭就得提到AbstractQueuedSynchronizer同步框架,一個僅僅須要簡單繼承就能夠實現複雜線程的同步方案,建議你們去研究一下。segmentfault

四、閉鎖(CountDownLatch)

  • 介紹:閉鎖是一種同步工具,能夠延遲線程的進度直到終止狀態。能夠把它理解爲一扇門,當閉鎖到達結束狀態以前,這扇門一直是關閉的,沒有任何線程能夠經過。當閉鎖到達結束狀態時,這扇門會打開並容許全部線程經過,而且閉鎖打開後不可再改變狀態。
    閉鎖能夠確保某些任務直到其餘任務完成後才繼續往下執行。緩存

  • 使用介紹
    • 構造器中的計數值(count)實際上就是閉鎖須要等待的線程數量。這個值只能被設置一次,並且CountDownLatch沒有提供任何機制去從新設置這個計數值。
    • 與CountDownLatch的第一次交互是主線程等待其餘線程。主線程必須在啓動其餘線程後當即調用CountDownLatch.await()方法。這樣主線程的操做就會在這個方法上阻塞,直到其餘線程完成各自的任務。
    • 其餘N 個線程必須引用閉鎖對象,由於他們須要通知CountDownLatch對象,他們已經完成了各自的任務。這種通知機制是經過 CountDownLatch.countDown()方法來完成的;每調用一次這個方法,在構造函數中初始化的count值就減1。因此當N個線程都調 用了這個方法,count的值等於0,而後主線程就能經過await()方法,恢復執行本身的任務。

四、柵欄(CyclicBarrier)

  • 介紹:柵欄相似於閉鎖,它能阻塞一組線程直到某個事件的發生。柵欄與閉鎖的關鍵區別在於,全部的線程必須同時到達柵欄位置,才能繼續執行。閉鎖用於等待事件,而柵欄用於等待其餘線程。CyclicBarrier可使必定數量的線程反覆地在柵欄位置處聚集。當線程到達柵欄位置時將調用await方法,這個方法將阻塞直到全部線程都到達柵欄位置。若是全部線程都到達柵欄位置,那麼柵欄將打開,此時全部的線程都將被釋放,而柵欄將被重置以便下次使用。安全

  • CyclicBarrier和CountDownLatch的區別:
    • CountDownLatch的計數器只能使用一次,而CyclicBarrier的計數器可使用reset()方法重置,可使用屢次,因此CyclicBarrier可以處理更爲複雜的場景;
    • CyclicBarrier還提供了一些其餘有用的方法,好比getNumberWaiting()方法能夠得到CyclicBarrier阻塞的線程數量,isBroken()方法用來了解阻塞的線程是否被中斷;
    • CountDownLatch容許一個或多個線程等待一組事件的產生,而CyclicBarrier用於等待其餘線程運行到柵欄位置。

五、原子量 (Atomic)

  • 介紹:Atomic一詞跟原子有點關係,後者曾被人認爲是最小物質的單位。計算機中的Atomic是指不能分割成若干部分的意思。若是一段代碼被認爲是Atomic,則表示這段代碼在執行過程當中,是不能被中斷的。一般來講,原子指令由硬件提供,供軟件來實現原子方法(某個線程進入該方法後,就不會被中斷,直到其執行完成)多線程

  • 特性:在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具備排他性,即當某個線程進入方法,執行其中的指令時,不會被其餘線程打斷,而別的線程就像自旋鎖同樣,一直等到該方法執行完成,才由JVM從等待隊列中選擇一個另外一個線程進入,這只是一種邏輯上的理解。其實是藉助硬件的相關指令來實現的,不會阻塞線程(或者說只是在硬件級別上阻塞了)。併發

  • 注意:原子量雖然能夠保證單個變量在某一個操做過程的安全,但沒法保證你整個代碼塊,或者整個程序的安全性。所以,一般還應該使用鎖等同步機制來控制整個程序的安全性。

六、Condition

  • 介紹:在Java程序中,任意一個Java對象,都擁有一組監視器方法(定義在java.lang.Object類上),主要包括wait()、wait(long)、notify()、notifyAll()方法,這些方法與synchronized關鍵字配合,能夠實現等待/通知模式。Condition接口也提供了相似Object的監視器方法,與Lock配合能夠實現等待/通知模式,可是這二者在使用方式以及功能特性上仍是有區別的。
  • Condition與Object中的wati,notify,notifyAll區別:
    • Condition中的await()方法至關於Object的wait()方法,Condition中的signal()方法至關於Object的notify()方法,Condition中的signalAll()至關於Object的notifyAll()方法。
      不一樣的是,Object中的這些方法是和同步鎖捆綁使用的;而Condition是須要與互斥鎖/共享鎖捆綁使用的。
    • Condition它更強大的地方在於:可以更加精細的控制多線程的休眠與喚醒。對於同一個鎖,咱們能夠建立多個Condition,在不一樣的狀況下使用不一樣的Condition。若是採用Object類中的wait(),notify(),notifyAll()實現該緩衝區,當向緩衝區寫入數據以後須要喚醒"讀線程"時,不可能經過notify()或notifyAll()明確的指定喚醒"讀線程",而只能經過notifyAll喚醒全部線程(可是notifyAll沒法區分喚醒的線程是讀線程,仍是寫線程)。 可是,經過Condition,就能明確的指定喚醒讀線程。

七、併發編程概覽

concurrent.png

6、總結

  • 到此線程的基本內容介紹就差很少了,這篇文章偏理論一些,每一個知識點的介紹並不全面。
  • 你們能夠以此篇文章爲索引,來展開對併發編程的深刻學習,細細咀嚼每一個知識點,相信你會有巨大的收穫!!!

我的博客地址:

cnblogs:https://www.cnblogs.com/baixianlong
csdn:https://blog.csdn.net/tiantuo6513
segmentfault:https://segmentfault.com/u/baixianlong
github:https://github.com/xianlongbai

相關文章
相關標籤/搜索