天天看點

《Java多線程程式設計核心技術》讀書筆記第一章  多線程技能第二章  對象及變量的并發通路第三章 線程間通信第四章 Lock的使用

第一章  多線程技能

一、多線程的建立

        1、內建Thread類

        2、實作Runnable接口

二、線程的方法

        1、currentThread()

                擷取目前運作的線程。

                該方法将傳回代碼段正在被哪個線程調用的資訊。

        2、isAlive()

                判斷目前的線程是否處于活動狀态。

                如果線程未start(),傳回false;

                如果線程start(),未運作結束,傳回true;

                如果線程已經運作結束,傳回false;

        3、sleep()

                在指定的毫秒數内讓目前“正在執行的線程”休眠(暫停執行)。

                這個“正在執行的線程”是指 this.currentThread() 傳回的線程。

        4、getId()

                擷取線程的唯一辨別。

三、停止線程的相關方法

        1、interrupt()

                該方法僅僅是在目前線程打了一個标記,标記為中斷狀态,并不是真的停止線程。

                如果想要停止線程,需要自己線上程類run()方法中通過擷取目前線程的狀态,進行判斷和處理。

                在sleep()狀态下interrupt線程,會抛出InterruptedException,并清除interrupt狀态。

                先把線程打上interrupt()标記,再讓線程sleep(),同樣會抛出InterruptedException,并清除interrupt狀态。

                總結:隻要一個線程同時滿足interrupt()狀态和sleep()狀态,就會抛出InterruptedException,并且會清除interrupt狀态。

        2、interrupted()

                測試目前線程是否已經中斷。

                執行本方法後,将會自動清除掉目前線程的interrupt()狀态,即将已經打過interrupt()标記的線程,變為沒有interrupt()标記。

        3、isInterrupted()

                測試線程是否已經中斷。不會改變interrupt()狀态。

        4、異常法

                在run()方法中,通過判斷this.interrupted(),抛出一個異常,以此打斷線程的正常執行順序。

                同時,在run()方法中,try()catch()異常。

        5、暴力停止——stop()

                該方法已經被廢棄。該方法将會抛出ThreadDeath異常,此異常不需要顯示地捕捉。

                廢棄原因:

                                ①有可能使一些請理性的工作得不到完成

                                ②對鎖定的對象進行了“解鎖”,導緻資料得不到同步的處理,出現資料不一緻的問題。

        6、return

                與interrupt()配合使用,和異常法相似。

                更加推薦使用異常法,因為過多的return将會污染程式,而使用異常流能更好、更友善地控制程式的運作流程。

四、暫停線程的相關方法

        1、suspend()

                使一個線程進入暫停狀态,可恢複運作。

                suspend()狀态的線程isAlive()為true。

        2、resume()

                讓一個陷入暫停狀态的線程恢複運作。

        3、suspend()和resume()的缺點

                ①獨占

                在使用公共的同步對象,也就是加鎖對象時,由于自身陷入了暫停狀态,鎖得不到釋放,其他線程也就無法擷取加鎖對象了。

                例:System.out.println();

                ②不同步

                因為線程的暫停而導緻資料不同步。

                方法執行一半被暫停了,方法的目的是改變對象的兩個屬性,結果剛改變了一個屬性就被暫停,導緻了資料的不同步。

        4、yield()

                放棄目前的CPU資源,将它讓給其他的任務去占用CPU執行時間。

                有可能剛剛放棄,馬上又獲得CPU時間片。      

五、線程的優先級

        1、setPriority()

                線程可以劃分優先級,優先級較高的線程得到的CPU資源較多。

                設定線程優先級有助于幫“線程規劃期”确定下一次選擇哪一個線程來優先執行。

                在Java中,線程的優先級分為1~10這10個等級,如果小于1或者大于10,則JDK将會抛出異常throw new IllegalArgumentException()。

                JDK中使用三個常量來預置定義優先級的值,代碼如下:

/**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;
           

        2、繼承特性

                在Java中,線程的優先級具有繼承性,比如A線程啟動B線程,則B線程的優先級與A是一樣的。

        3、規則性

                CPU盡量将執行資源讓給優先級比較高的線程。

        4、随機性

                線程的優先級與代碼執行順序無關。

                優先級較高的線程不一定先執行完。

六、守護線程

        Java線程中有兩種線程,一種是使用者線程,另一種就是守護(Daemon)線程。

        1、什麼是守護線程?

                守護線程是一種特殊的線程,它的特性有陪伴的含義,當程序中不存在非守護線程了,則守護線程自動銷毀。

                典型的守護線程就是垃圾回收線程(GC),當程序中沒有非守護線程了,則垃圾回收線程也就沒有存在的必要了,自動銷毀。

第二章  對象及變量的并發通路

一、synchronized同步方法

        1、A線程先持有object對象的Lock鎖,B線程可以以異步的方式調用object對象中的非synchronized類型的方法。

        2、A線程先持有object對象的Lock鎖,B線程如果需要在這時調用object對象中的synchronized類型的方法則需等待,也就是同步。

        3、髒讀:隻對指派操作加鎖,未對取值操作加鎖。

        4、synchronized鎖重入

                ①前提

    • 不在函數内使用靜态或全局資料。
    • 不傳回靜态或全局資料,所有資料都由函數的調用者提供。
    • 使用本地資料(工作記憶體),或者通過制作全局資料的本地拷貝來保護全局資料。
    • 不調用不可重入函數

                ②可重入與線程安全

                         可重入的函數一定是線程安全的,反之則不一定成立。

                ③synchronized可重入鎖的實作

       每個鎖關聯一個線程持有者和一個計數器。當計數器為0時表示該鎖沒有被任何線程持有,那麼任何線程都可能獲得該鎖而調用相應方法。當一個線程請求成功後,JVM會記下持有鎖的線程,并将計數器計為1。此時其他線程請求該鎖,則必須等待。而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增。當線程退出一個synchronized方法/塊時,計數器會遞減,如果計數器為0則釋放該鎖。

                ④可重入鎖具有繼承性

當存在父子類繼承關系時,子類是完全可以通過“可重入鎖”調用父類的同步方法的。

這裡的繼承性,不是指的方法,是指的鎖。

        5、出現異常,鎖自動釋放

        6、同步不具有繼承性

                父類某個方法加同步鎖,子類繼承父類,子類無鎖。  

二、synchronized同步語句塊

        1、synchronized(this)代碼塊是鎖定目前對象的

        2、可以将任意對象作為對象螢幕

        3、靜态同步synchronized方法與synchronized(class)代碼塊

                ①synchronized加到static靜态方法上是給Class類上鎖。

                ②synchronized加到非static靜态方法上是給對象上鎖。

        4、不使用String作為鎖對象

        5、同步synchronized方法無限等待與解決

                如果一個類有多個方法都持有目前對象當鎖,若其中一個方法進入死循環,其他同步對象将陷入無限等待。

                解決:都用new Object()當鎖,方法之間異步。

        6、死鎖問題

                ①死鎖是程式設計的Bug,在設計程式時就要避免雙方互相持有對方的鎖的情況。

                ②死鎖問題與鎖是否嵌套無關,隻要互相等待對方釋放鎖就有可能出現死鎖。

                在jdk安裝目錄的bin目錄下,執行jps檢視目前運作的main方法所在類的程序id,

                再輸入jstack -l id檢視執行結果。

        7、内部類與靜态内部類

                synchronized綁定了相同對象就是同步的,綁定不同對象就是異步的。

                針對同步方法,隻要對象不變,即使對象的屬性被改變,運作的結果還是同步。

三、volatile關鍵字

        關鍵字volatile的主要作用是使變量在多個線程間可見。

        線程安全包含原子性和可見性兩個方面。volatile解決的是可見性,但是在JDK1.7之前才有用。

  1. volatile重要工作是避免線程髒讀:當線程對volatile變量進行讀操作時,會先将自己工作記憶體中的變量置為無效,之後再通過主記憶體拷貝新值到工作記憶體中使用。
  2. volatile解決的是變量在多個線程之間的可見性,但不能完全保證資料的原子性。
  3. 現在JVM經過優化,已不會出現liveness failure 。是以沒事别用volatile。

第三章 線程間通信

一、等待/通知機制

    1、wait()

            釋放鎖,可以加時間自動喚醒。

    2、notify()

            随機喚醒一個以參數為鎖的線程,但是在目前線程的方法執行完之前,不會釋放鎖。

    3、notifyAll()

            喚醒所有線程。

    4、通過管道進行線程間的通信:位元組流

            在Java的JDK中提供了4個類來使線程間可以進行通信:

            1)PipedInputStream 和 PipedOutputStream

            2)PipedReader 和 PipedWriter

二、join的使用

    在很多情況下,主線程建立并啟動子線程,如果子線程中要進行大量的耗時運算,主線程往往将早于子線程結束之前結束。這時,如果主線程想等待子線程執行完成之後再結束,比如子線程處理一個資料,主線程要取得這個資料中的值,就要用到join()方法了。

    方法join()的作用是等待線程對象銷毀。

    1、作用

            方法join的作用,是使對象x正常執行run()方法中的任務,而使目前線程z進行無限期的阻塞,等待線程x銷毀後再繼續執行線程z後面的代碼。

            方法join具有使線程排隊運作的作用,有些類似同步的運作效果。

    2、join與synchronized的差別是:

                    join在内部使用wait()方法進行等待,而sychronized關鍵字使用的是“對象螢幕”原理做為同步。

    3、join()方法與interrupt()方法如果彼此遇到,則會出現異常。

    4、join(long)具有釋放鎖的特點。

三、類ThreadLocal的使用

    變量值的共享可以使用public static變量的形式,所有的線程都使用同一個public static 變量。如果想實作每一個線程都有自己的共享變量該如何解決呢?JDK中提供的類ThreadLocal正是為了解決這個問題。

    類ThreadLocal主要解決的就是每個線程綁定自己的值,可以将ThreadLocal類比喻成全局存放資料的盒子,盒子中可以存儲每個線程的私有資料。

    1、ThreadLocal解決的是變量在不同線程間的隔離性,也就是不同線程擁有自己的值,不同線程中的值可以放入ThreadLock類中進行儲存。

    2、get()

            預設傳回null,可以通過繼承ThreadLocal重寫initialValue()方法的方式,修改預設傳回值。

四、類InheritableThreadLocal的使用

    使用類InheritableThreadLocal可以在子線程中取得父線程繼承下來的值。

第四章 Lock的使用

一、使用ReentrantLock類

    1、擷取鎖的方法

           a) lock(), 如果擷取了鎖立即傳回,如果别的線程持有鎖,目前線程則一直處于休眠狀态,直到擷取鎖;

           b) tryLock(), 如果擷取了鎖立即傳回true,如果别的線程正持有鎖,立即傳回false;

           c) tryLock(long timeout,TimeUnit unit), 如果擷取了鎖定立即傳回true,如果别的線程正持有鎖,會等待參數給定的時間,在等待的過程中,如果擷取了鎖定,就傳回true,如果等待逾時,傳回false;

           d) lockInterruptibly:如果擷取了鎖定立即傳回,如果沒有擷取鎖定,目前線程處于休眠狀态,直到或者鎖定,或者目前線程被别的線程中斷;

    2、unlock()方法

            釋放鎖,最好放在finally代碼塊中執行。

    3、Condition對象

  • Object類中的wait()方法,相當于Condition類中的await()方法。二者都會釋放鎖。
  • Object類中的wait(long timeout)方法相當于Condition類中的await(long time, TimeUnit unit)方法。
  • Object類中的notify()方法相當于Condition類中的signal()方法。notify()不會釋放鎖,signal()會釋放鎖。
  • Object類中的notifyAll()方法相當于Condition類中的signalAll()方法。

    4、公平鎖與非公平鎖

            公平鎖:線程擷取鎖的順序是按照線程加鎖的順序來配置設定的,即先來先得的FIFO先進先出順序。

            非公平鎖:搶占機制,随機獲得鎖。

            通過new ReentrantLock(true)可以擷取公平鎖,new ReentrantLock(false)可以擷取非公平鎖。

            new ReentrantLock()是非公平鎖。

    5、方法的講解

  • int getHoldCount():查詢目前線程保持此鎖定的個數,也就是調用lock()方法的次數。
  • int getQueueLength():傳回正在等待擷取此鎖定的線程估計數。比如有5個線程,1個線程執行await()方法,那麼在調用getQueueLength()方法後,傳回值是4,說明有4個線程同時在等待lock的釋放。
  • int getWaitQueueLength(Condition condition):傳回等待與次鎖定相關的給定條件Condition的線程估計數。比如有5個線程,每個線程都執行了同一個condition對象的await()方法,則調用本方法的傳回值是5。
  • boolean hasQueuedThread(Thread thread):查詢指定的線程是否正在等待擷取此鎖定。
  • boolean hasQueuedThread():查詢是否有線程正在等待擷取此鎖定。
  • boolean hasWaiters(Condition condition):查詢是否有線程正在等待與此鎖定有關的condition條件。
  • boolean isFair():判斷是不是公平鎖。
  • boolean isHeldByCurrentThread():查詢目前線程是否保持此鎖定。
  • boolean isLocked():查詢此鎖定是否由任意線程保持。
  • void lockInterruptibly():如果目前線程未被中斷,則擷取鎖定,如果已經被中斷則出現異常。
  • boolean tryLock():僅在調用時鎖定未被另一個線程保持的情況下,才擷取該鎖定。
  • boolean tryLock(long timeout, TimeUnit unit):如果鎖定在給定等待時間内沒有被另一個線程保持,且目前線程未被中斷,則擷取該鎖定。
  • void awaitUninterruptibly():使用await()方法,且不會抛出InterruptedException異常。
  • boolean awaitUntil(Date deadline):指定時間自己喚醒自己,也可以被提前喚醒。

二、使用ReentrantReadWriteLock類

    1、讀讀共享

    2、寫寫互斥

    3、讀寫互斥

    4、寫讀互斥

繼續閱讀