Java多線程筆記[未更新完]

最近課上可摸魚時間較多,所以併發開坑學習java

本篇學習自Java多線程編程實戰指南編程

目前進展:剛開坑,處於理解概念階段緩存


本篇學習自Java多線程編程實戰指南安全

Q.進程和線程的區別服務器

進程Process是程序運行的實例,在Java的範疇中,運行一個Java程序的實質是啓動一個JVM進程,也就是一個運行的Java程序就是一個Java虛擬機進程(Java Web服務器是一個進程同時運行多個Java Web應用)多線程

進程是程序向操做系統申請資源(內存空間、文件句柄等)的基本單位,而線程Thread是進程中可獨立運行的最小單位,一個進程能夠包含多個線程,同一個進程中的全部線程共享該進程的資源,併發

Q.extends Thread 和 Runnable的區別學習

1.面向對象來看,前者是繼承實現,後者是組合實現,所以後者有更低的耦合程度優化

2.從對象共享來看,Runnable意味着多個線程可共享同一個Runnable實例this

一種具備迷惑性的例子,在例子中兩種非空構造的Thread構造均可以有一致的hashCode的緣由是構造聲明是Thread(Runnable),而Thread的JDK聲明是public class Thread implements Runnable,後兩種例子都是體現Runnable的特性而非區分二者

class XjbThreadImpl implements Runnable {
    
    public void run() {
        System.out.println("Implements: "+this.hashCode());
    }
}

class XjbThreadExt extends Thread {
    
    public void run() {
        System.out.println("Extends: "+this.hashCode());
    }
}
//
public class Main {
    
   public static void main(String[] args) {
        for(int i = 0; i < 2; i++) {
            new Thread(new XjbThreadImpl()).start();
            new Thread(new XjbThreadExt()).start();
        }
        // 使用時請分別註釋上下兩部分
        Runnable tmpImpl = new XjbThreadImpl();
        Thread impl0 = new Thread(tmpImpl);
        Thread impl1 = new Thread(tmpImpl);
        Thread tmpExt = new XjbThreadExt();
        Thread ext0 = new Thread(tmpExt);
        Thread ext1 = new Thread(tmpExt);
        impl0.start();
        impl1.start(); //hashCode一致
        ext0.start();
        ext1.start(); //hashCode一致
   }
}

3.從建立成原本看,建立線程實例比Runnable成本更高,JVM須要爲它分配調用棧空間、內核線程等資源

Java的線程分爲守護線程(Daemon)和用戶線程,前者用於執行重要性不高的任務,後者反之,且只有當全部用戶進程結束後JVM纔會正常中止(System.exit調用除外)

Thread的方法

.join() 等待相應進程結束/ yield()當前線程主動放棄處理器的佔用

Q.線程的生命週期狀態

NEW 已建立而爲啓動的線程,線程只有一次機會處於該狀態

RUNNABLE 包括兩個自狀態READYRUNNING,前者表示處於改狀態的線程能夠被Scheduler調度而處於RUNNING。後者表示正在運行(即run正在被處理器執行),可使用yield()返回到READY。(活躍子狀態)

BLOCKED 線程發起阻塞式IO或申請鎖(被其餘線程佔有)時處於該狀態,該狀態不佔用處理器資源。當完成阻塞式IO操做或得到鎖時從新回到RUNNALE

WAITING 線程執行特定方法後處於等待其餘線程執行另外特定操做的狀態。好比Object.wait(),Thread.join(),LockSupport.park(Object)能夠得到該狀態,返回RUNNABLE的方法有Object.notify()/notifyAll(),LockSupport.unpark(Object)

TIMED_WAITING有時間限制的等待狀態,如Thread.sleep(long),Object.wait(long),超時自動返回RUNNABLE

TERMINATED 已經執行結束的進程處於該狀態(只有一次),既Thread.run()調用結束、

Q.併發與並行的區別

並行Parallel多個線程在同一時刻處理任務

併發Concurrent單個線程交替的完成不一樣任務

(書裏說的仍是不太明確,下次查查別的資料)

競態指計算的正確性依賴相對時間順序或者線程的交錯,容易產生髒讀問題(讀取到一個過世的數據、丟失更新)

競態的模式:read-modify-write(好比a++)和check-then-act(好比對變量的if-else),注意這些都是對於共享變量而言的,局部變量(如形參)和synchronized不會形成競態

PS.提一下a++的過程,1.read:將變量a的值從內存讀到寄存器r中,2.將寄存器r的值+1,3.將寄存器r的內容寫入變量a對應的內存空間

若是某線程在執行到第二步的時候變量a的值已經更新了,那它就是髒讀(舊數據),接着到第三部覆蓋到內存空間的操做就是丟失更新

解決競態:上鎖/局部變量 (Q.有沒有更好的方法?

競態不必定會形成錯誤結果,拿上面的模式來講,若是是交錯執行read(Thread-0)-read(Thread-1)或者check(Thread-0)-check(Thread-1),很顯然結果依然正確

Synchronized使修飾的方法在任一時刻只能被一個線程執行

Q.什麼是線程安全

若是一個類在單線程環境下額可以正常運行,而且在多線程環境下,使用方沒必要爲其作任何改變的狀況下也能運行正常,那就稱其線程安全

線程安全表如今原子性、可見性和有序性

原子性:說白了就是「不可分割」,細分爲兩層含義

1.對於涉及共享變量的操做,在操做之外的線程看來是不可分割的,那就是原子操做(儘管對於正在執行的線程來講是分不少步驟,但在外界看來該操做要麼還沒有開始,要麼已經結束)

2.訪問同一組共享變量的非只讀原子操做是不能被線程交錯執行的

原子性只在多線程以及線程共享的變量如下討論纔有意義

實現原子性的兩種Solutions:1.Lock軟件實現鎖 2.CAS硬件實現鎖

Java語言規範中規定6種基礎類型的變量和引用變量的寫操做都是原子性的

(long/double除外,緣由是32位的JVM對64位的寫操做可能會分解成低32和高32處理,仍是有衝突的可能,好消息是volatile下是確保原子性的)

PS.關於讀寫的原子性和volatile的關係

1.volatile只保證變量寫操做的原子性,不保證兩種競態模式的原子性

2.Java針對任何變量的讀操做均符合原子性

Q.你tm還記得哪8種基礎類型嗎

A.byte boolean short char int long float double

可見性:多線程環境下,一個線程對某個共享變量進行更新後,後續訪問該變量的縣城可能沒法馬上讀取該更新的結果(可能永遠讀取不了),讀取不了則是不可見

問題產生的可能:JIT優化對多線程形成不利(書裏提了個循環不變式外提的優化,水平太菜不敢評論)、共享變量分配到寄存器(兩個線程運行在不一樣處理器上,一個處理器沒法讀取另外一個處理器的寄存器)等

保證可見性:volatile

volatile做用之一是提示JIT編譯器被修飾的變量可能被多個線程共享,阻止不恰當的優化

另外一做用是讀取volatile關鍵字修飾的變量會使相應的處理器執行刷新處理器緩存的動做(停留在寫緩衝器是不可見的,要求被寫入到高速緩存或主內存)

可見性只保證線程能讀取到相對新值(更新後其餘線程能獲得更新後的值),不保證最新值(讀取該變量的線程在讀取使用時其它線程沒法更新,該線程讀取到的相對新值爲最新值)

PS.單處理器也會存在可見性問題

雖然寄存器都在同一處理器中,但多線程是經過時間片 分配實現的,在上下文切換的過程當中,一個線程對寄存器變量的修改會被線程上下文保存,另外一線程沒法得知該修改

有序性:(玄學

volatile的另外一做用是禁止重排序,保證有序性

PS.我用不大準確的詞語概括一下

原子性:線程之間是否能任意穿插

可見性:線程執行中是否時刻得知更新

上下文切換:多個線程共享同一個處理器,不一樣線程被選中開始或繼續執行實現併發,切入切出過程當中須要保存和恢復相應進程的進度信息(執行到哪一條指令)稱爲上下文,通常包括通用寄存器和程序計數器的內容

在Java的角度來看,從RUNNABLE到非RUNNABLE狀態的切換過程就是一個上下文切換(暫停or恢復)

自發性上下文切換可從線程的狀態轉移圖(各類方法、鎖、IO)得出

非自發性上下文切換由線程調度器引發,好比GC過程對JVM堆進行整理時要求中止全部應用線程

然而,上下文切換是須要開銷的,所以並不是線程越多越厲害,計算效率可能更加慘淡

一些活性故障的例子:

死鎖:不解釋

鎖死:拿到鎖的線程跑路了,資源永遠不釋放

活鎖:線程處於RUNNABLE但執行的任務沒有進展(帶薪摸魚)

飢餓:線程沒法得到所需資源而沒法執行任務

非公平調度優勢:吞吐率大、減小上下文切換次數(公平調度會帶來更多的暫停和喚醒);缺點:申請資源的時間誤差大、可能致使飢餓,通常首選非公平調度

鎖機制的思路:將多個線程對共享數據的併發訪問轉換爲串行訪問

JVM對鎖的劃分有內部鎖(Synchronized實現)和顯式鎖(實現Lock接口的ReentrantLock類實現)

鎖的做用是保障線程安全,體如今上面提到的三個方面

1.經過互斥實現原子性,對外在線程來講是不可分割的(由於沒法得到鎖)

2.鎖的得到隱含刷新處理器緩存的動做,使得讀線程在臨界區前將寫線程對共享變量的更新同步到該線程執行處理器的高速緩衝中(可見性+原子性保證了最新值)

3.寫線程在臨界區執行的操做在讀線程所執行的臨界區看起來是按源代碼順序執行的(原子性保證讀線程沒法區分具體順序,還和可見性保證了最新值,所以感知上是按順序執行的)

last update:19/03/26

相關文章
相關標籤/搜索