老馬的JVM筆記(七)----Java內存模型與線程

人不好分心,機器必須分心。

7.1 Java內存模型(Java Memory Model, JMM)

Java內存模型的主要目標爲定義Java程序中各個變量的訪問規則,即在虛擬機中如何存儲和讀取變量。這裏的變量是針對所有Java變量的。變量存儲在虛擬機的主內存上,所有。工作內存爲線程的內存,包括局部變量,都是從主內存的變量中拷貝而來。線程不可以直接讀寫主內存的變量,線程之間的工作變量也不可以隨意訪問。

主內存與工作內存之間通過協議交互,非常嚴格。Java規定了工作內存可以執行的8種操作,每種都需要原子性。

lock:作用於主內存,將變量標誌爲某一線程獨佔。

unlock:作用於主內存,從lock中釋放出來。

read:作用於主內存,將主內存變量傳輸到工作內存。

load:作用於工作內存,把read出的變量值放入變量副本中。(工作內存只能用拷貝)

use:作用於工作內存,把工作內存中的變量值傳輸給執行引擎,就是用工作內存中的拷貝值。

assign:作用於工作內存,把執行引擎收到的值賦給工作內存中的變量(拷貝來的)。

store:作用於工作內存,把工作內存的變量值傳輸給主內存。

write:作用於主內存,把工作內存store的值寫入主內存的變量中。

                  read              load             use

主內存  <----------------> 工作內存 <--------------> 執行引擎

                  write              store           assign          

與主內存相關的操作,不允許單獨出現。read必須與load成對出現,write必須與store成對出現,且先後順序必須對。lock與unlock也必須成對出現。

7.2 volatile型變量

與synchronized相比,volatile更輕量。變量被定義爲volatile後,所有線程可見---初值可見,被更改後也可以被所有線程得知最新的值。但不能保證併發安全。

    public static volatile int race = 0;

    public static void inc(){
        race++;
    }

    public static void main(String[] args){
        Thread[] threads = new Thread[20];
        for(int i = 0; i < 20; i++){
                    threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i = 0; i< 1000; i++){
                        inc();
                    }
                }
            });
            threads[i].start();


        }

        while(Thread.activeCount() > 1) Thread.yield();

        System.out.println(race);

    }

按作者說法,這段代碼會出現比20000小的數,在實際操作中,只會在一個線程中卡死。作者想表達race++鑲嵌在increase()中,操作非原子性,會出現指令出現檢查不符的情況,導致數值無法更新。按作者的說法,volatile的實用性在於可以實現可見性,但不可以依賴當前變量的值,且變量的不變約束不依賴於其他變量。簡單說就是不能指望這個變量的值是可靠的場景,可以使用volatile,比如不計算,只當成flag。

volatile變量禁止指令重排序優化。在Java編譯中,爲了優化,會改變一些語句的執行順序,但不會影響賦值等重要操作,但提前執行可能會影響代碼邏輯。這種問題會比較致命---明明邏輯是對的,咋就先跑了後面的語句呢?所以遇到邏輯有問題的時候,可以用volatile把變量拴起來,這樣至少針對該變量的語句是符合順序的。在volatile變量中,load必須和use牢牢連在一起,並在之前要刷新;assign和store要牢牢連在一起,立刻write。

volatile在讀變量值時與普通變量速度類似,寫的時候因爲涉及lock,會慢。但會比最靠譜的synchronized快。volatile不能算鎖。

特別的,long和double兩個長類型在load、store、read、write可以不保證原子性。就是說在未經保護的情況下,幾個線程同時操作,有可能讀取出誰也不認識的值,理論存在,實際沒有。

7.4 Happens before原則

好像到處都有這個原則)規定了一些Java中一定要遵循的邏輯順序:

程序次序規則(Program Order Rule):在一個線程內要按照代碼順序(?),準確來說是按照控制流順序(√)。線程間才存               在無序的概念。

管程鎖定規則(Monitor Lock Rule):同一個鎖,unlock的操作要在lock之後。

volatile變量規則(Volatile Variable Rule):volatile修飾的變量的寫操作要在之後對這個變量的讀操作之前,改完才能讀。

線程啓動規則(Thread Start Rule):Thread對象的start()要在這個線程所有動作之前。

線程終止規則(Thread Termination Rule):線程所有動作都在終止之前,終止包括join,isAlive()。

線程中斷規則(Thread Interruption Rule):線程對interrupt()的調用要早於該線程中檢測到中斷事件前。

對象終結規則(Finalizer Rule):一個對象的初始化(構造函數執行結束)要先行發生於finalize()之前。

傳遞性(Transitivity):A先行發生於B,B先行發生於C,則A先行發生於C。

其實看起來就是一些非常理所當然的邏輯順序,多的確實保證不了,靠雙手。

7.5 線程的實現

Java的Thread類的關鍵方法都聲明爲Native,Native的方法未使用或無法使用平臺無關的手段實現。

1.使用內核線程實現:

內核線程(Kernel-Level Thread,KLT)就是操作系統的內核支持的線程。 內核通過調度器(Scheduler)分配一個線程哪裏給誰,將線程的任務映射到各個處理器上。多核就是多線程。程序一般不會直接使用系統線程,而是使用內核線程的一種搞機接口,輕量級進程(Light Weight Process,LWP),這是常見的線程。內核線程和輕量級進程是一個1:1的關係。由系統內核線程實現,導致在線程操作時,會進行系統調用,成本較高,且消耗系統資源。

2.使用用戶線程實現:

廣義上講,非內核線程的線程都可以被稱爲用戶線程(User Thread,UT),輕量級“進程”也可以屬於用戶線程。狹義來說,用戶線程是建立在用戶控件的線程庫上,內核不能感知線程的存在。操作都在用戶態中完成,不需要系統內核的幫助。進程與線程可以達到1:N的效果,性能較高。由於缺少系統內核的支持,很多操作如阻塞,線程映射到其他處理器上等問題需要解決,很複雜。Java是不用了。

3.使用用戶線程加輕量級進程混合實現:

在組合的實現中,用戶線程以及有關操作依然在用戶態中,避免了系統調用。輕量級進程則作爲用戶線程與內核線程之間交流的橋樑,完成線程的調度和處理器的映射。在這種關係中,內核線程與輕量級進程依然是1:1的關係,但輕量級進程與用戶線程是一種不定的N:M的關係。一個輕量級進程可以服務多個用戶線程。

7.6 Java線程調度

線程調度這裏指系統爲線程分配處理器的調度過程,分爲“協同式線程調度”(Cooperative Thread-Scheduling)和“搶佔式線程調度”(Preemptive Threads-Scheduling)。協同式屬於人民當家做主,線程自己安排自己的起止,在完成後主動讓給下一個線程。實現簡單,看似合理,但豈不是亂了套?自行安排會導致執行時間不可控,一旦阻塞,將連任到死,導致系統崩潰。搶佔式由系統分配時間,線程本身無法決定。Java使用搶佔式線程調度。當一個進程阻塞過長,直接殺掉。

Java定義了線程的6種原子級狀態,任意時間點每個線程只能處於一種狀態:

1.新建(new):創建後爲啓動。

2.運行(Runnable):包括了準備和運行中的狀態,所以runnable翻譯成“運行”可能一點點不合適。執行中或等待分配都算。

3.無限期等待(Waiting):線程處於睡眠狀態,不會被CPU分配時間,除非有其他線程將其喚醒。(無timeout的wait(),join(),以及LockSupport.park()方法)

4.限期等待(Timed Waiting):不會被CPU分配時間,但當過了時限後會被自動喚醒。(sleep(),設置了timeout的wait(),join()等)

5.阻塞(Blocked):阻塞狀態的線程等待其他線程中排他鎖的釋放,在同步區的線程會進入此狀態。

6.結束(Terminated):線程已終止。

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