天天看點

Java—多線程相關知識

多線程相關知識

      • 目錄
        • 前言
        • 一. 程序與線程的簡單了解
        • 二. 線程排程
        • 三. 建立線程的方式
        • 四. 用Runnable還是Thread?
        • 五. start()方法和run()方法的差別
        • 六. Thread的實作涉及到哪種設計模式
        • 七. 線程狀态
        • 八. 線程相關方法
            • 8.1 相關方法
            • 8.2 例子
        • 九. 線程安全與線程同步
        • 十. 線程同步的方法
            • 10.1 同步代碼塊
            • 10.2 同步方法
            • 10.3 Lock鎖
            • 10.3 synchronized與Lock鎖對比
        • 十一. 死鎖
        • 十二. 線程通信

目錄

前言

        本文隻是涉及到多線程的基本知識,未對線程池,并發做研究。

        相關參部落格和視訊:

https://www.jianshu.com/p/512ab59ee584

https://www.cnblogs.com/hongmoshui/p/11109982.html

https://www.cnblogs.com/yangyongjie/p/11024712.html

https://blog.csdn.net/qq_36711757/article/details/82384356

https://www.bilibili.com/video/BV1V4411p7EF?from=search&seid=14826241534626431382

一. 程序與線程的簡單了解

        一個程式就是一個程序,系統運作一個程式即使一個程序從建立,運作到消亡的過程。而一個程式中的多個任務則被稱為線程。

        程序表示資源配置設定的基本機關,線程是程序執行運算的最小機關,也是排程運作的基本機關。

二. 線程排程

        分時排程:所有線程輪流使用CPU的使用權,平均配置設定每個線程占用CPU的時間。

        搶占式排程:優先級高的線程先使用CPU,如果優先級相同則随機選擇一個。

三. 建立線程的方式

        繼承Thread類建立線程類

        通過Runnable接口建立線程類

        通過Callable和Future建立線程(不常用,少見)。

        一般就是通過繼承Thread類或者調用Runnable接口來重寫run()方法實作線程,調用start()方法啟動線程。

四. 用Runnable還是Thread?

        一般建議使用Runnable接口,因為java是單繼承的,實作Runnable接口可以避免這個單繼承的局限性

五. start()方法和run()方法的差別

        這也是面試常見問題,涉及到對java線程模型的了解程度。start()方法被用來啟動新建立的線程,而且start()内部調用了run()方法,這和直接調用run()方法的效果不一樣。當你調用run()方法時,隻會在原來的線程中調用,沒有開啟新的線程,隻有shart()方法才會啟動新線程。

六. Thread的實作涉及到哪種設計模式

        源代碼中Thread的實作是靜态代理。靜态代理就是比如,你結婚,你會和新娘宣讀誓言,但是除了這,還有布置現場,主持流程這些都是由婚慶公司來做,婚慶公司就是代理,來幫你處理結婚的事情。

        而線程也是類似的,比如我們自己寫個搶票的線程,搶票這個類會實作Runnable接口,同時Thread類也是實作Runnable接口的,然後回調用Thread.start()來幫忙搶票,這個Thread就是代理,相當于婚慶公司。具體了解可以在以後寫線程代碼時細細體會。

七. 線程狀态

        1,New初始狀态:線程對象被建立後,就進入了建立狀。例如,Thread thread=new Thread()。

        2,Runnable就緒狀态:線程對象被建立後,該對象的start()方法被調用了,該線程啟動,處于就緒狀态,随時可被CPU排程執行。并不是調用start()方法後馬上線程就運作了,要等cpu來調用了,才會開始運作。

        3,Running運作狀态:先後擷取CPU權限進行執行,需要注意的是,線程隻能從就緒狀态進入到與性能狀态。

        4,阻塞狀态:阻塞狀态時線程因為某種原因放棄CPU使用權,暫時停止運作。直到線程進入就緒狀态,才有機會轉到阻塞狀态。阻塞的情況分三種:

               1) 等待阻塞—通過調用線程wait()方法,讓線程等待某工作的完成。

               2)同步阻塞—線程在擷取synchronized同步鎖失敗(因為鎖被其他線程所占用),它會進入阻塞狀态。

               3)其他阻塞—通過調用線程sleep()或者join()或發出了I/O請求時,線程會進入阻塞狀态。當sleep()狀态逾時,join()等待線程中止或者逾時、或者I/O處理完畢時,線程重新轉入就緒狀态。

        5,Dead死亡狀态:線程執行完了或者因異常退出了run()方法,該線程結束生命周期。

Java—多線程相關知識
Java—多線程相關知識

八. 線程相關方法

8.1 相關方法

        sleep()方法:在指定的毫秒數内讓目前正在執行的線程休眠。線程休眠不會釋放鎖。抱着鎖睡覺

        join()方法:簡單了解就是插隊,vip,先完成這個線程,再執行其他線程,其他線程阻塞。

        setPriority:設定線程優先級,優先級高的,被cpu調用的可能性增大(隻是可能性大)。

        yield()方法:線程禮讓,讓當先正在執行的線程暫停,但不阻塞,将線程從運作狀态轉為就緒狀态,讓cpu重新調用。禮讓也是不一定成功的,看cpu心情。

        線程停止方法:不推薦使用JDK提供的stop()、destory()方法,推薦讓線程自己停止下來。一般使用一個标志位來幫忙中止線程(while(flag)…)。

        wait()方法,notify()方法:wait()方法的作用是讓需要鎖的線程等待,直到其他線程調用notify()或着notifyAll()喚醒方法。與sleep()方法的不同是,sleep()方法睡眠時,保持對象鎖,仍然占有該鎖,而wait()睡眠時,釋放對象鎖。

8.2 例子

/* 

經典面試題:建立三個線程,A線程列印10次A,B線程列印10次B,C線程列印10次C,要求線程同時運作,交替列印10次ABC。

這個問題用Object的wait(),notify(),sleep()就可以很友善的解決

*/

class Thread1 implements  Runnable {

    private String name;
    private Object prev;
    private Object self;
    
    private Thread1(String name, Object prev, Object self) {
        this.name = name;
        this.prev = prev;
        this.self = self;
    }
    @Override

    public void run() {
        int count = 10;
        while (count > 0) {
            synchronized (prev) {
                synchronized (self) {
                    System.out.print(name);
                    count--;
                    self.notify();
                }
                try {
                    prev.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) throws Exception {
        Object a = new Object();
        Object b = new Object();
        Object c = new Object();
        Thread1 pa = new Thread1("A", c, a);
        Thread1 pb = new Thread1("B", a, b);
        Thread1 pc = new Thread1("C", b, c);

        new Thread(pa).start();
        Thread.sleep(100);  //確定按順序A、B、C執行
        new Thread(pb).start();
        Thread.sleep(100);
        new Thread(pc).start();
        Thread.sleep(100);
    }
}

/*結果:ABCABCABCABCABCABCABCABCABCABC*/
           

九. 線程安全與線程同步

        當我們使用多個線程通路同一資源的時候,且多個線程對資源有寫的操作,那麼就容易出現線程安全問題。在Java中為解決線程提供了同步機制。

        線程安全的核心理念是“要麼隻讀,要麼加鎖”

        計算機的線程同步,是指線程之間按照某種機制協調先後執行次序,即當有一個線程再對記憶體操作時,其他線程都不可以對這個記憶體操作,一直等待直到該線程完成操作,其他線程才能對該記憶體進行操作。

        實作線程同步的方式有很多,比如同步方法,鎖,阻塞隊列等。

十. 線程同步的方法

10.1 同步代碼塊

        synchronized(['sɪŋkrənaɪzd] 同步)關鍵字可以用于方法中的某個區塊,表示隻對這個區塊的資源實行互斥通路。

synchronized(Obj){
  需要同步操作的代碼
}
//這個Obj可以是任何對象,但是推薦共享資源作為Obj
           

        可以了解為給對象上了個鎖。多個線程對象,要使用同一把鎖,任何時候,最多允許一個線程擁有同步鎖,誰拿到鎖誰就進入代碼塊,其他的線程隻能在外等待。

        在多線稱競争情況下,加鎖,釋放鎖會導緻比較多的上下文切換和排程示範等問題,性能下降。

10.2 同步方法

        使用synchronized修飾的方法就叫做同步方法,保證A線程在執行的時候,其他線程隻能在外面等着。

        方法裡面又需要修改的内容才需要鎖,鎖的太多也容易浪費資源。

10.3 Lock鎖

        JDK5.0後,提供了更加強大的線程同步機制——通過顯式定義同步鎖對象來實作同步。同步鎖使用Lock對象充當。

10.3 synchronized與Lock鎖對比

        Lock是顯式鎖,需要手動的開啟和關閉,synchronized是隐式鎖,出了作用域就自動釋放。

        Lock隻有代碼塊鎖,synchronized有代碼塊和方法鎖。

        使用Lock鎖,JVM将花費較少的時間來排程線程,性能更好,并且具有更好的擴充性。

        優先使用順序:Lock> 同步代碼塊 >同步方法。

十一. 死鎖

        多個線程同時各自占有一些資源,并且互相等待着其他線程占有的資源才能允許,而導緻兩個或者多個線程都在等待着對方釋放資源,都停止執行的情況。

        那麼如何避免死鎖呢:死鎖的産生有四個必要條件:互斥條件,請求與保持條件,不剝奪條件,循環等待條件。隻要破環任何一個就可以。比如我們可以指定擷取鎖的順序。

十二. 線程通信

        具體的沒搞太明白,涉及到了線程池,生産者消費者模式(這不屬于設計模式的一種,隻是一種問題類型的描述)