java核心-多線程(4)-線程類基礎知識

###1.併發java

<1>使用併發的一個重要緣由是提升執行效率。因爲I/O等狀況阻塞,單個任務並不能充分利用CPU時間。因此在單處理器的機器上也應該使用併發。
<2>爲了實現併發,操做系統層面提供了。可是進程的數量和開銷都有限制,而且多個進程之間的數據共享比較麻煩。另外一種比較輕量的併發實現是使用線程,一個進程能夠包含多個線程。線程在進程中沒有數量限制, 數據共享相對簡單。線程的支持跟語言是有關係的。Java 語言中支持多線程。
<3>Java 中的多線程是搶佔式的。這意味着一個任務隨時可能中斷並切換到其它任務。因此咱們須要在代碼中足夠的謹慎,防範好這種切換帶來的反作用。

###2.基礎緩存

<1>Runnable 它能夠理解成一個任務。它的run()方法就是任務的邏輯,執行順序;
    <2>Thread 它是一個任務的載體,虛擬機經過它來分配任務執行的時間片。Thread中的start方法能夠做爲一個併發任務的入口。不經過start方法來執行任務,那麼run方法就只是一個普通的方法;
    <3>線程的狀態
        見下圖
    <4>Callable<T> 它是一個帶返回的異步任務,返回的結果放到一個Future對象中。
    <5>Future<T> 它能夠接受Callable任務的返回結果。在任務沒有返回的時候調用get方法會阻塞當前線程。cancel方法會嘗試取消未完成的任務(未執行->直接不執行,已經完成->返回false,正在執行->嘗試中斷)。
    <6>FutureTask<T> 同時繼承了Runnable, Callable 接口。
    <7>Java 1.5以後,再也不推薦直接使用Thread對象做爲任務的入口。推薦使用Executor管理Thread對象。Executor是線程與任務之間的的一箇中間層,它屏蔽了線程的生命週期,再也不須要顯式的管理線程。而且ThreadPoolExecutor 實現了此接口,咱們能夠經過它來利用線程池的優勢。
    <8>線程池涉及到的類有:Executor, ExecutorService, ThreadExecutorPool, Executors, FixedThreadPool, CachedThreadPool, SingleThreadPool。
    <9>Executor 只有一個方法,execute來提交一個任務;
    <10>ExecutorService 提供了管理異步任務的方法,也能夠產生一個Future對象來跟蹤一個異步任務。
主要的方法以下:
•	submit 能夠提交一個任務
•	shutdown 能夠拒絕接受新任務
•	shutdownNow 能夠拒絕新任務並向正在執行的任務發出中斷信號
•	invokeXXX 批量執行任務

    <11>ThreadExecutorPool 線程池的具體實現類。線程池的好處在於提升效率,能避免頻繁申請/回收線程帶來的開銷。
它的使用方法複雜一些,構造線程池的可選參數有:
1.	corePoolSize : int 工做的Worker的數量。
2.	maximumPoolSize : int 線程池中持有的Worker的最大數量
3.	keepAliveTime : long 當超過Workder的數量corePoolSize的時候,若是沒有新的任務提交,超過corePoolSize的Worker的最長等待時間。超過這個時間以後,一部分Worker將被回收。
4.	unit : TimeUnit keepAliveTime的單位
5.	workQueue : BlockingQueue 緩存任務的隊列, 這個隊列只緩存提交的Runnable任務。
6.	threadFactory : ThreadFactory 產生線程的「工廠」
7.	handler : RejectedExecutionHandler 當一個任務被提交的時候,若是全部Worker都在工做而且超過了緩存隊列的容量的時候。會交給這個Handler處理。Java 中提供了幾種默認的實現,AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy。

    <12>Executors類提供了幾種默認線程池的實現方式。
1.	CachedThreadExecutor 工做線程的數量沒有上限(Integer的最大值), 有須要就建立新線程。
2.	FixedThreadExecutor 預先一次分配固定數量的線程,以後再也不須要建立新線程。
3.	SingleThreadExecutor 只有一個線程的線程池。若是提交了多個任務,那麼這些人物將排隊,每一個任務都在上一我的物執行完以後執行。全部任務都是按照它們的提交順序執行的。

    <13>sleep(long) 當前線程 停止 一段時間。它不會釋放鎖。Java1.5以後提供了更加靈活的版本。TimeUnit 能夠指定睡眠的時間單位。

    <14>優先級 絕大多數狀況下咱們都應該使用默認的優先級。不一樣的虛擬機中對應的優先級級別的總數,通常用三個就能夠了 MAX_PRIORITY, NORM_PRIORITY, MIN_PRIORITY。

    <15>讓步 Thread.yield()建議相同優先級的其它線程先運行,可是不保證必定運行其它線程。

    <16>後臺線程 一個進程中的全部非後臺線程都終止的時候整個進程也就終止,同時殺死全部後臺線程。與優先級沒有什麼關係。

    <17>join() 線程 A 持有線程T,當在線程T調用T.join()以後,A會阻塞,直到T的任務結束。能夠加一個超時參數,這樣在超時以後線程A能夠放棄等待繼續執行任務。

    <18>捕獲異常 不能跨線程捕獲異常。好比說不能在main線程中添加try-catch塊來捕獲其它線程中拋出的異常。每個Thread對象均可以設置一個UncaughtExceptionHandler對象來處理本線程中拋出的異常。線程池中能夠經過參數ThreadFactory來爲每個線程設置一個UncaughtExceptionHandler對象。

###3.訪問共享資源多線程

<1>在處理併發的時候,將變量設置爲private很是的重要,這能夠防止其它線程直接訪問變量。
    <2>synchronized 修飾方法在不加參數狀況下,使用對象自己做爲鎖。靜態方法使用Class對象做爲鎖。同一個任務能夠屢次得到對象鎖。
    <3>顯式鎖 Lock,相比synchronized更加靈活。可是須要的代碼更多,編寫出錯的可能性也更高。只有在解決特殊問題或者提升效率的時候才用它。
    <4>原子性 原子操做就是永遠不會被線程切換中斷的操做。不少看似原子的操做都是非原子的,好比說long,double是由兩個byte表示的,它們的全部操做都是非原子的。因此,涉及到併發異常的地方都加上同步吧。除非你對虛擬機十分的瞭解。
    <5>volatile 這個關鍵字的做用在於防止多線程環境下讀取變量的髒數據。這個關鍵字在c語言中也有,做用是相同的。
    <6>原子類 AtomicXXX類,它們可以保證對數據的操做是知足原子性的。這些類能夠用來優化多線程的執行效率,減小鎖的使用。然而,使用難度仍是比較高的。
    <7>臨界區 synchronized關鍵字的用法。不是修飾整個方法,而是修飾一個代碼塊。它的做用在於儘可能利用併發的效率,減小同步控制的區域。
    <8>ThreadLocal 這個概念與同步的概念不一樣。它是給每個線程都建立一個變量的副本,並保持副本之間相互獨立,互不干擾。因此各個線程操做本身的副本,不會產生衝突。

###4.終結任務併發

<1>一個線程不是能夠隨便中斷的。即便咱們給線程設置了中斷狀態,它也仍是能夠得到CPU時間片的。只有由於sleep()方法而阻塞的線程能夠當即收到InterruptedException異常,因此在sleep中斷任務的狀況下能夠直接使用try-catch跳出任務。其它狀況下,均須要經過判斷線程狀態來判斷是否須要跳出任務(Thread.interrupted()方法)。
    <2>synchronized方法修飾的代碼不會在收到中斷信號後當即中斷。ReentrantLock鎖控制的同步代碼能夠經過InterruptException中斷。
    <3>Thread.interrupted方法調用一次以後會當即清空中斷狀態。能夠本身用變量保存狀態。

###5.線程協做異步

<1>wait/notifyAll wait/notifyAll是Object類中的方法。調用wait/notifyAll方法的對象是互斥對象。由於Java中全部的Object均可以作互斥量(synchronized關鍵字的參數),因此wait/notify方法是在Object類中的。
    <2>wait與sleep 不一樣在於sleep方法是Thread類中的方法,調用它的時候不會釋放鎖;wait方法是Object類中的方法,調用它的時候會釋放鎖。
           調用wait方法以前,當前線程必須持有這段邏輯的鎖。不然會拋出異常,不能繼續執行。
    <3>wait方法能夠將當前線程放入等待集合中,並釋放當前線程持有的鎖。此後,該線程不會接收到CPU的調度,並進入休眠狀態。有四種狀況肯能打破這種狀態:
1.  有其它線程在此互斥對象上調用了notify方法,而且恰好選中了這個線程被喚醒;
2.  有其它線程在此互斥對象上調用了notifyAll方法;
3.  其它線程向此線程發出了中斷信號;
4.  等待時間超過了參數設置的時間。線程一旦被喚醒以後,它會像正常線程同樣等待以前持有的全部鎖。直到恢復到wait方法調用以前的狀態。還有一種不常見的狀況,spurious wakeup(虛假喚醒)。就是在沒有notify,notifyAll,interrupt的時候線程自動醒來。查了一些資料並無弄清楚是爲何。不過爲了防止這種現象,咱們要在wait的條件上加一層循環。

    <4> 當一個線程調用wait方法以後,其它線程調用該線程的interrupt方法。該線程會喚醒,並嘗試恢復以前的狀態。當狀態恢復以後,該線程會拋出一個異常。
            notify 喚醒一個等待此對象的線程。
            notifyAll 喚醒全部等待此對象的線程。

###6.錯失信號工具

<1>當兩個線程使用notify/wait或者notifyAll/wait進行協做的時候,不恰當的使用它們可能會致使一些信號丟失。
         見下圖
信號丟失是這樣發生的:
當T2執行到Point1的時候,線程調度器將工做線程從T2切換到T1。T1完成T2條件的設置工做以後,線程調度器將工做線程從T1切換回T2。雖然T2線程等待的條件已經知足,但仍是會被掛起。

    <2>解決的方法比較簡單:
         見下圖
將競爭條件放到while循環的外面便可。在進入while循環以後,在沒有調用wait方法釋放鎖以前,將不會進入到T1線程形成信號丟失。
    
    <3>Condition 他是concurrent類庫中顯式的掛起/喚醒任務的工具。它是真正的鎖(Lock)對象產生的一個對象。其實用法跟wait/notify是一致的。await掛起任務,signalAll()喚醒任務。
    <4>生產者消費者隊列 Java中提供了一種很是簡便的容器,BlockingQueue。已經幫你寫好了阻塞式的隊列。除了BlockingQueue,使用PipedWriter/PipedReader也能夠方便的在線程之間傳遞數據

###7.死鎖優化

<1>死鎖有四個必要條件,打破一個便可去除死鎖。
         四個必要條件:
1.  互斥條件。 互斥條件:一個資源每次只能被一個進程使用。
2.  請求與保持條件:一個線程因請求資源而阻塞時,對已得到的資源保持不放。
3.  不剝奪條件:線程已得到的資源,在末使用完以前,不能強行剝奪。
4.  循環等待條件:若干線程之間造成一種頭尾相接的循環等待資源關係

###8.其餘工具操作系統

<1>CountDownLatch 同步多個任務,強制等待其它任務完成。它有兩個重要方法countDown,await以及構造時傳入的參數SIZE。當一個線程調用await方法的時候會掛起,直到該對象收到SIZE次countDown。一個對象只能使用一次。
    <2>CyclicBarrier 也是有一個SIZE參數。當有SIZE個線程調用await的時候,所有線程都會被喚醒。能夠理解爲全部運動員就位後才能起跑,早就位的運動員只能掛起等待。它能夠重複利用。
    <3>DelayQueue 一個無界的BlockingQueue,用來放置實現了Delay接口的對象,在隊列中的對象只有在到期以後才能被取走。若是沒有任何對象到期,就沒有頭元素。
    <4>PriorityBlockingQueue 一種自帶優先級的阻塞式隊列。
    <5>ScheduledExecutor 能夠把它想象成一種線程池式的Timer, TimerTask。
    <6>Semaphore 互斥鎖只容許一個線程訪問資源,可是Semaphore容許SIZE個線程同時訪問資源。
    <7>Exchanger 生產者消費者問題的特殊版。兩個線程能夠在都‘準備好了’以後交換一個對象的控制權。
    <8>ReadWriteLock 讀寫鎖。 讀-讀不互斥,讀-寫互斥,寫-寫互斥。

以上來自《think in java》線程

相關文章
相關標籤/搜索