對《Java高併發編程詳解:多線程與架構設計》書做一個總結

1.問題分析工具

jconsole

jstack

jstat

jvisualvm

jProfiler

前4個工具是在jdk的bin目錄下的,最後一個是一個收費的工具。

還有一個class字節碼反編譯命令:javap -c class文件名

2.線程生命週期的狀態

new:線程的創建。

Runnable:可運行狀態,就是線程調用了start方法。

Blocked:阻塞狀態,線程調用start方法後,但是還沒獲取到鎖對象,所以進入了阻塞狀態。

Wait:等待狀態,當線程調用了start方法後並獲取到了所對象,此時只需要等待CPU的調度(得到CPU的執行時間片),在這裏個人的理解是:線程執行了wait方法後該線程的狀態我也歸爲wait狀態。萬事俱備只欠東風。

計時等待:當線程裏面執行了sleep(millis)方法後,該線程沒有釋放鎖對象,並等待指定時間後重新獲取CPU的執行時間片使得到CPU的調度。

3.線程的start方法剖析:模板設計模式在Thread中的應用

其實Thread的run和start就是比較典型的模板設計模式,父類編寫算法結構代碼,子類實現邏輯細節。(具體可以看該書P9)

4.Runnable接口的引入以及策略模式在Thread中的使用

在很多軟文以及一些書籍中,經常提到,創建線程的方式有兩種,第一種是構造Thread類的對象或Thread子類的對象,第二種是實現Runnable接口,這種說法我覺得是錯誤的不嚴謹。因爲在JDK中代表線程的只有Thread這個類。

解釋一下我爲什麼認爲實現Runnable接口不是創建線程的另一種方式

public class MyThread implements Runnable {

    @Override

    public void run() {

        //線程要執行的邏輯

    }

    public static void main(String[] args) {

        MyThread myThread = new MyThread();

        Thread t = new Thread(myThread);

        t.start();

    }

}

Thread類的run方法:

    @Override

    public void run() {

        //target是創建Thread對象時,通過構造方法的參數傳進來的Runnable接口實現類的對象

        //在這裏就是上面例子中的MyThread的對象

        //當target不爲空時,線程其實執行的就是Runnable實現類的對象的run方法

        if (target != null) {

            target.run();

        }

    }

所以,通過實現Runnable接口來創建線程的方式,線程依然是Thread類,只是換了一種方式讓線程來執行我們寫的邏輯代碼,這種方式的好處是:MyThread這樣的類本來就需要繼承一個類,但是又要實現線程要處理的邏輯,所以就可以採用實現Runnable接口的方法。但線程依然是Thread類的對象,而Runnable的run只是可以作爲線程的執行單元。

4.4策略模式在Thread中的使用

再來看一下Thread類的run方法:

 

    @Override

    public void run() {

        if (target != null) {

            //創建每一個Thread對象並通過構造參數傳入Runnable接口的實現了

            //最終調用Runnable接口實現類的run方法

            //其實傳入的不同的Runnable接口實現類的對象,就相當於不同的策略

            target.run();

        }

    }

5.JVM內存結構

 

1.程序計數器:程序計數器在JVM中所起的作用就是用於存放當前線程接下來將要執行的字節碼指令、分支、循環、跳轉、異常處理等信息。在任何時候,一個處理器只執行其中一個線程的命令,爲了能夠在CPU時間片輪轉切換上下文之後順利回到正確的執行位置,每條線程都需要具有一個獨立的程序計數器,各個線程之間互不影響,因此JVM將此塊內存區域設計成了線程私有的。

2.Java虛擬機棧:Java虛擬機站也是線程私有的,它的生命週期與線程相同,是在JVM運行時所創建的。在線程中,方法在執行的時候都會創建一個名爲棧幀(stack frame)的數據結構,主要用於存放局部變量表、操作棧、動態鏈接、方法出口等信息。方法的調用也對應着棧幀在虛擬機中的壓棧和彈棧過程。

webpuploading.4e448015.gif轉存失敗重新上傳取消

3.本地方法棧:爲調用Java本地方法服務的。Java中提供了調用本地方法的接口(Java Native Interface),也就是C/C++程序,在線程的執行過程中,經常會碰到調用JNI方法的情況,JVM爲本地方法所劃分的內存區域便是本地方法棧。這塊內存區域其自由度非常高,完全靠不同的JVM廠商來實現,Java虛擬機規範並未給出強制性的規定,同樣它也是縣城私有的內存區域。

4.對內存:堆內存是JVM中最大的一塊內存區域,被所有的線程共享,Java在運行期間創建的所有對象幾乎都存放在該內存區域,該內存區域也是垃圾回收器重點照顧的區域,因此有些時候被稱爲“GC堆”。

對內存一般被細分爲新生代和老年代,更細緻劃分:新生代中包含Eden區、S1和S2區。

在這裏簡單講一下堆的垃圾回收和空間比例(HotSpotJVM爲例)

新生代使用的垃圾回收算法是:複製算法

老年代使用的垃圾回收算法是:標記-清除-整理算法

Eden、S1和S2分別佔用新生代空間的默認比例:8:1:1

5.方法區:方法區也是被線程共享的內存區域,他主要用於存儲已經被虛擬機加載的類信息、常量、靜態變量、即時編譯器(JIT)編譯後的代碼等數據。

6.使用TimeUnit替代Thread.sleep

7.正常關閉線程

7.1通過interrupt中斷線程

7.2定義一個volatile修飾的變量,通過判斷該變量的值來終止線程。

8.線程死鎖

8.1交叉鎖可導致程序出現死鎖

public class DeadLock

{

    private final Object MUTEX_READ = new Object();

    private final Object MUTEX_WRITE = new Object();

    public void read()

    {

        synchronized (MUTEX_READ)

        {

            System.out.println(currentThread().getName() + " get READ lock");

            synchronized (MUTEX_WRITE)

            {

                System.out.println(currentThread().getName() + " get WRITE lock");

            }

            System.out.println(currentThread().getName() + " release WRITE lock");

        }

        System.out.println(currentThread().getName() + " release READ lock");

    }

    public void write()

    {

        synchronized (MUTEX_WRITE)

        {

            System.out.println(currentThread().getName() + " get WRITE lock");

            synchronized (MUTEX_READ)

            {

                System.out.println(currentThread().getName() + " get READ lock");

            }

            System.out.println(currentThread().getName() + " release READ lock");

        }

        System.out.println(currentThread().getName() + " release WRITE lock");

    }

    public static void main(String[] args)

    {

        final DeadLock deadLock = new DeadLock();

        new Thread(() ->

        {

            while (true)

            {

                deadLock.read();

            }

        }, "READ-THREAD").start();

        new Thread(() ->

        {

            while (true)

            {

                deadLock.write();

            }

        }, "WRITE-THREAD").start();

    }

}

9.鎖對象關聯的monitor對象

    public void a() {

        //每個對象的對象頭會關聯鎖的信息,這個信息可以理解爲就是對應的monitor對象。

        //也就是每個對象都有一個與之關聯的monitor對象,這個對象中有兩個隊列

        //一個EntryList和WaitSet兩個隊列。

        //EntryList保存 向這個鎖對象加鎖沒加成功的線程(因爲前面的線程對鎖對象還沒釋放)。

        //WaitSet保存 調用了這個所對象的wait方法進入了等待狀態的線程。

        synchronized (obj) {

            //todo

            //obj.wait

        }

    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章