Java多線程編程總結

來源:互聯網
上載者:User

標籤:亂序執行   nts   print   author   intel   使用者   cpu   範圍   切換   

Java線程:概念與原理 Java線程:建立與啟動 Java線程:線程棧模型與線程的變數 Java線程:線程狀態的轉換   Java線程:線程的同步與鎖 Java線程:線程的互動 Java線程:線程的調度-休眠   Java線程:線程的調度-優先順序 Java線程:線程的調度-讓步 Java線程:線程的調度-合并 Java線程:線程的調度-守護線程 Java線程:線程的同步-同步方法 Java線程:線程的同步-同步塊   Java線程:並發協作-生產者消費者模型 Java線程 :並發協作-死結 Java線程:volatile關鍵字 Java線程:新特徵-線程池 Java線程:新特徵-有傳回值的線程 Java線程:新特徵-鎖(上) Java線程:新特徵-鎖(下) Java線程:新特徵-訊號量 Java線程:新特徵-阻塞隊列 Java線程:新特徵-阻塞棧 Java線程 :新特徵-條件變數 Java線程:新特徵-原子量 Java線程:新特徵-障礙器 Java線程:大總結  ------------------------------------------------------------------------------------------------- 下面的內容是很早之前寫的,內容不夠充實,而且是基於Java1.4的內容,Java5之後,線程並發部分擴充了相當多的內容,因此建議大家看上面的系列文章的內容,與時俱進,跟上Java發展的步伐。 -------------------------------------------------------------------------------------------------  一、認識多任務、多進程、單線程、多線程要認識多線程就要從作業系統的原理說起。 以前古老的DOS作業系統(V 6.22)是單任務的,還沒有線程的概念,系統在每次只能做一件事情。比如你在copy東西的時候不能rename檔案名稱。為了提高系統的利用效率,採用批處理來批量執行任務。 現在的作業系統都是多任務作業系統,每個啟動並執行任務就是作業系統所做的一件事情,比如你在聽歌的同時還在用MSN和好友聊天。聽歌和聊天就是兩個任務,這個兩個任務是“同時”進行的。一個任務一般對應一個進程,也可能包含好幾個進程。比如啟動並執行MSN就對應一個MSN的進程,如果你用的是windows系統,你就可以在工作管理員中看到作業系統正在啟動並執行進程資訊。 一般來說,當運行一個應用程式的時候,就啟動了一個進程,當然有些會啟動多個進程。啟動進程的時候,作業系統會為進程分配資源,其中最主要的資源是記憶體空間,因為程式是在記憶體中啟動並執行。在進程中,有些程式流程塊是可以亂序執行的,並且這個代碼塊可以同時被多次執行。實際上,這樣的代碼塊就是線程體。線程是進程中亂序執行的代碼流程。當多個線程同時啟動並執行時候,這樣的執行模式成為並發執行。 多線程的目的是為了最大限度的利用CPU資源。 Java編寫程式都運行在在Java虛擬機器(JVM)中,在JVM的內部,程式的多任務是通過線程來實現的。每用java命令啟動一個java應用程式,就會啟動一個JVM進程。在同一個JVM進程中,有且只有一個進程,就是它自己。在這個JVM環境中,所有程式碼的運行都是以線程來運行。 一般常見的Java應用程式都是單線程的。比如,用java命令運行一個最簡單的HelloWorld的Java應用程式時,就啟動了一個JVM進程,JVM找到程式程式的進入點main(),然後運行main()方法,這樣就產生了一個線程,這個線程稱之為主線程。當main方法結束後,主線程運行完成。JVM進程也隨即退出 。 對於一個進程中的多個線程來說,多個線程共用進程的記憶體塊,當有新的線程產生的時候,作業系統不分配新的記憶體,而是讓新線程共用原有的進程塊的記憶體。因此,線程間的通訊很容易,速度也很快。不同的進程因為處於不同的記憶體塊,因此進程之間的通訊相對困難。 實際上,操作的系統的多進程實現了多任務並發執行,程式的多線程實現了進程的並發執行。多任務、多進程、多線程的前提都是要求作業系統提供多任務、多進程、多線程的支援。 在Java程式中,JVM負責線程的調度。線程調度是值按照特定的機製為多個線程分配CPU的使用權。調度的模式有兩種:分時調度和搶佔式調度。分時調度是所有線程輪流獲得CPU使用權,並平均分配每個線程佔用CPU的時間;搶佔式調度是根據線程的優先順序別來擷取CPU的使用權。JVM的線程調度模式採用了搶佔式模式。 所謂的“並發執行”、“同時”其實都不是真正意義上的“同時”。眾所周知,CPU都有個時鐘頻率,表示每秒中能執行cpu指令的次數。在每個刻度內,CPU實際上只能去執行一條(也有可能多條)指令。作業系統將進程線程進行管理,輪流(沒有固定的順序)分配每個進程很短的一段是時間(不一定是均分),然後在每個線程內部,程式碼自己處理該進程內部線程的時間分配,多個線程之間相互的切換去執行,這個切換時間也是非常短的。因此多任務、多進程、多線程都是作業系統給人的一種宏觀感受,從微觀角度看,程式的運行是非同步執行的。 用一句話做總結:雖然作業系統是多線程的,但CPU每一時刻只能做一件事,和人的大腦是一樣的,呵呵。  二、Java與多線程 Java語言的多線程需要作業系統的支援。 JAVA 虛擬機器允許應用程式並發地運行多個執行線程。Java語言提供了多線程編程的擴充點,並給出了功能強大的線程式控制制API。 在Java中,多線程的實現有兩種方式:擴充java.lang.Thread類實現java.lang.Runnable介面  每個線程都有一個優先順序,高優先順序線程的執行優先於低優先順序線程。每個線程都可以或不可以標記為一個精靈。當某個線程中啟動並執行代碼建立一個新 Thread 對象時,該新線程的初始優先順序被設定為建立線程的優先順序,並且若且唯若建立線程是守護線程時,新線程才是精靈。 當 JAVA 虛擬機器啟動時,通常都會有單個非守護線程(它通常會調用某個指定類的 main 方法)。JAVA 虛擬機器會繼續執行線程,直到下列任一情況出現時為止: 調用了 Runtime 類的 exit 方法,並且安全管理器允許退出操作發生。非守護線程的所有線程都已停止運行,無論是通過從對 run 方法的調用中返回,還是通過拋出一個傳播到 run 方法之外的異常。  三、擴充java.lang.Thread類 /** * File Name:   TestMitiThread.java * Created by:  IntelliJ IDEA. * Copyright:   Copyright (c) 2003-2006 * Company:     Lavasoft( [url]http://lavasoft.blog.51cto.com/[/url]) * Author:      leizhimin * Modifier:    leizhimin * Date Time:   2007-5-17 10:03:12 * Readme:      通過擴充Thread類實現多線程 */public class TestMitiThread {    public static void main(String[] rags) {        System.out.println(Thread.currentThread().getName() + " 線程運行開始!");        new MitiSay("A").start();        new MitiSay("B").start();        System.out.println(Thread.currentThread().getName() + " 線程運行結束!");    }} class MitiSay extends Thread {    public MitiSay(String threadName) {        super(threadName);    }     public void run() {        System.out.println(getName() + " 線程運行開始!");        for (int i = 0; i < 10; i++) {            System.out.println(i + " " + getName());            try {                sleep((int) Math.random() * 10);            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println(getName() + " 線程運行結束!");    }} 運行結果: main 線程運行開始!main 線程運行結束!A 線程運行開始!0 A1 AB 線程運行開始!2 A0 B3 A4 A1 B5 A6 A7 A8 A9 AA 線程運行結束!2 B3 B4 B5 B6 B7 B8 B9 BB 線程運行結束! 說明:程式啟動運行main時候,java虛擬機器啟動一個進程,主線程main在main()調用時候被建立。隨著調用MitiSay的兩個對象的start方法,另外兩個線程也啟動了,這樣,整個應用就在多線程下運行。 在一個方法中調用Thread.currentThread().getName()方法,可以擷取當前線程的名字。在mian方法中調用該方法,擷取的是主線程的名字。 注意:start()方法的調用後並不是立即執行多線程代碼,而是使得該線程變為可運行態(Runnable),什麼時候運行是由作業系統決定的。從程式啟動並執行結果可以發現,多線程程式是亂序執行。因此,只有亂序執行的代碼才有必要設計為多線程。Thread.sleep()方法調用目的是不讓當前線程獨自霸佔該進程所擷取的CPU資源,以留出一定時間給其他線程執行的機會。實際上所有的多線程代碼執行順序都是不確定的,每次執行的結果都是隨機的。  四、實現java.lang.Runnable介面 /** * 通過實現 Runnable 介面實現多線程 */public class TestMitiThread1 implements Runnable {     public static void main(String[] args) {        System.out.println(Thread.currentThread().getName() + " 線程運行開始!");        TestMitiThread1 test = new TestMitiThread1();        Thread thread1 = new Thread(test);        Thread thread2 = new Thread(test);        thread1.start();        thread2.start();        System.out.println(Thread.currentThread().getName() + " 線程運行結束!");    }     public void run() {        System.out.println(Thread.currentThread().getName() + " 線程運行開始!");        for (int i = 0; i < 10; i++) {            System.out.println(i + " " + Thread.currentThread().getName());            try {                Thread.sleep((int) Math.random() * 10);            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println(Thread.currentThread().getName() + " 線程運行結束!");    }} 運行結果: main 線程運行開始!Thread-0 線程運行開始!main 線程運行結束!0 Thread-0Thread-1 線程運行開始!0 Thread-11 Thread-11 Thread-02 Thread-02 Thread-13 Thread-03 Thread-14 Thread-04 Thread-15 Thread-06 Thread-05 Thread-17 Thread-08 Thread-06 Thread-19 Thread-07 Thread-1Thread-0 線程運行結束!8 Thread-19 Thread-1Thread-1 線程運行結束! 說明:TestMitiThread1類通過實現Runnable介面,使得該類有了多線程類的特徵。run()方法是多線程程式的一個約定。所有的多線程代碼都在run方法裡面。Thread類實際上也是實現了Runnable介面的類。在啟動的多線程的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出對象,然後調用Thread對象的start()方法來運行多線程代碼。實際上所有的多線程代碼都是通過運行Thread的start()方法來啟動並執行。因此,不管是擴充Thread類還是實現Runnable介面來實現多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程編程的基礎。 五、讀解Thread類API static int MAX_PRIORITY          線程可以具有的最高優先順序。static int MIN_PRIORITY          線程可以具有的最低優先順序。static int NORM_PRIORITY          分配給線程的預設優先順序。 構造方法摘要Thread(Runnable target)          分配新的 Thread 對象。Thread(String name)          分配新的 Thread 對象。  方法摘要static Thread currentThread()          返回對當前正在執行的線程對象的引用。 ClassLoader getContextClassLoader()          返回該線程的上下文 ClassLoader。 long getId()          返回該線程的標識符。 String getName()          返回該線程的名稱。 int getPriority()          返回線程的優先順序。 Thread.State getState()          返回該線程的狀態。 ThreadGroup getThreadGroup()          返回該線程所屬的線程組。static boolean holdsLock(Object obj)          若且唯若當前線程在指定的對象上保持監視器鎖時,才返回 true。 void interrupt()          中斷線程。static boolean interrupted()          測試當前線程是否已經中斷。 boolean isAlive()          測試線程是否處於活動狀態。 boolean isDaemon()          測試該線程是否為守護線程。 boolean isInterrupted()          測試線程是否已經中斷。 void join()          等待該線程終止。 void join(long millis)          等待該線程終止的時間最長為 millis 毫秒。 void join(long millis, int nanos)          等待該線程終止的時間最長為 millis 毫秒 + nanos 納秒。 void resume()          已淘汰。 該方法只與 suspend() 一起使用,但 suspend() 已經遭到反對,因為它具有容易導致鎖死的。有關更多資訊,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。 void run()          如果該線程是使用獨立的 Runnable 運行物件建構的,則調用該 Runnable 對象的 run 方法;否則,該方法不執行任何操作並返回。 void setContextClassLoader(ClassLoader cl)          設定該線程的上下文 ClassLoader。 void setDaemon(boolean on)          將該線程標記為守護線程或使用者線程。static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)          設定當線程由於未捕獲到異常而突然終止,並且沒有為該線程定義其他處理常式時所調用的預設處理常式。 void setName(String name)          改變線程名稱,使之與參數 name 相同。 void setPriority(int newPriority)          更改線程的優先順序。 void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)          設定該線程由於未捕獲到異常而突然終止時調用的處理常式。static void sleep(long millis)          在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行)。static void sleep(long millis, int nanos)          在指定的毫秒數加指定的納秒數內讓當前正在執行的線程休眠(暫停執行)。 void start()          使該線程開始執行;JAVA 虛擬機器調用該線程的 run 方法。 void stop()          已淘汰。 該方法具有固有的不安全性。用 Thread.stop 來終止線程將釋放它已經鎖定的所有監視器(作為沿堆棧向上傳播的未檢查 ThreadDeath 異常的一個自然後果)。如果以前受這些監視器保護的任何對象都處於一種不一致的狀態,則損壞的對象將對其他線程可見,這有可能導致任意的行為。stop 的許多使用都應由只修改某些變數以指示目標線程應該停止啟動並執行代碼來取代。目標線程應定期檢查該變數,並且如果該變數指示它要停止運行,則從其運行方法依次返回。如果目標線程等待很長時間(例如基於一個條件變數),則應使用 interrupt 方法來中斷該等待。有關更多資訊,請參閱《為何不贊成使用 Thread.stop、Thread.suspend 和 Thread.resume?》。 void stop(Throwable obj)          已淘汰。 該方法具有固有的不安全性。請參閱 stop() 以獲得詳細資料。該方法的附加危險是它可用於產生目標線程未準備處理的異常(包括若沒有該方法該線程不太可能拋出的已檢查的異常)。有關更多資訊,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。 void suspend()          已淘汰。 該方法已經遭到反對,因為它具有固有的容易導致鎖死的。如果目標線程掛起時在保護關鍵系統資源的監視器上保持有鎖,則在目標線程重新開始以前任何線程都不能訪問該資源。如果重新開始目標線程的線程想在調用 resume 之前鎖定該監視器,則會發生死結。這類死結通常會證明自己是“凍結”的進程。有關更多資訊,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。 String toString()          返回該線程的字串表示形式,包括線程名稱、優先順序和線程組。static void yield()          暫停當前正在執行的線程對象,並執行其他線程。  六、線程的狀態轉換圖 線程在一定條件下,狀態會發生變化。線程變化的狀態轉換圖如下: 1、建立狀態(New):新建立了一個線程對象。2、就緒狀態(Runnable):線程對象建立後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待擷取CPU的使用權。3、運行狀態(Running):就緒狀態的線程擷取了CPU,執行程式代碼。4、阻塞狀態(Blocked):阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:(一)、等待阻塞:啟動並執行線程執行wait()方法,JVM會把該線程放入等待池中。(二)、同步阻塞:啟動並執行線程在擷取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池中。(三)、其他阻塞:啟動並執行線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態逾時、join()等待線程終止或者逾時、或者I/O處理完畢時,線程重新轉入就緒狀態。5、死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。  七、線程的調度1、調整線程優先順序:Java線程有優先順序,優先順序高的線程會獲得較多的運行機會。 Java線程的優先順序用整數表示,取值範圍是1~10,Thread類有以下三個靜態常量:static int MAX_PRIORITY          線程可以具有的最高優先順序,取值為10。static int MIN_PRIORITY          線程可以具有的最低優先順序,取值為1。static int NORM_PRIORITY          分配給線程的預設優先順序,取值為5。 Thread類的setPriority()和getPriority()方法分別用來設定和擷取線程的優先順序。 每個線程都有預設的優先順序。主線程的預設優先順序為Thread.NORM_PRIORITY。線程的優先順序有繼承關係,比如A線程中建立了B線程,那麼B將和A具有相同的優先順序。JVM提供了10個線程優先順序,但與常見的作業系統都不能很好的映射。如果希望程式能移植到各個作業系統中,應該僅僅使用Thread類有以下三個靜態常量作為優先順序,這樣能保證同樣的優先順序採用了同樣的調度方式。 2、線程睡眠:Thread.sleep(long millis)方法,使線程轉到阻塞狀態。millis參數設定睡眠的時間,以毫秒為單位。當睡眠結束後,就轉為就緒(Runnable)狀態。sleep()平台移植性好。 3、線程等待:Object類中的wait()方法,導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行為等價於調用 wait(0) 一樣。 4、線程讓步:Thread.yield() 方法,暫停當前正在執行的線程對象,把執行機會讓給相同或者更高優先順序的線程。 5、線程加入:join()方法,等待其他線程終止。在當前線程中調用另一個線程的join()方法,則當前線程轉入阻塞狀態,直到另一個進程運行結束,當前線程再由阻塞轉為就緒狀態。 6、線程喚醒:Object類中的notify()方法,喚醒在此對象監視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,並在對實現做出決定時發生。線程通過調用其中一個 wait 方法,在對象的監視器上等待。 直到當前的線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。類似的方法還有一個notifyAll(),喚醒在此對象監視器上等待的所有線程。 注意:Thread中suspend()和resume()兩個方法在JDK1.5中已經廢除,不再介紹。因為有容易導致鎖死的。 7、常見線程名詞解釋主線程:JVM調用程式mian()所產生的線程。當前線程:這個是容易混淆的概念。一般指通過Thread.currentThread()來擷取的進程。後台線程:指為其他線程提供服務的線程,也稱為守護線程。JVM的記憶體回收線程就是一個後台線程。前台線程:是指接受後台線程服務的線程,其實前台後台線程是聯絡在一起,就像傀儡和幕後操縱者一樣的關係。傀儡是前台線程、幕後操縱者是後台線程。由前台線程建立的線程預設也是前台線程。可以通過isDaemon()和setDaemon()方法來判斷和設定一個線程是否為後台線程。 

本文出自 “熔 岩” 部落格,請務必保留此出處http://lavasoft.blog.51cto.com/62575/27069

Java多線程編程總結

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.