Java基礎篇多線程概念和使用原理分析

轉載自:http://blog.yoodb.com/yoodb/article/detail/1330
首選瞭解一下什麼是進程和線程的概念?
進程是運行中的應用程序,每個進程都有自己獨立的一塊內存空間,一個進程可以啓動多個線程,而線程是指進程中的一個執行流程,也可以理解成一段代碼。比如java.exe進程中可以運行很多線程,線程永遠屬於某個進程,進程中的多個線程共享進程的內存。

在Java中爲了創建一個新的線程,必須指明線程所要執行的代碼。通過Java提供的類java.lang.Thread來方便多線程編程,這個類提供了大量的方法來方便我們控制自己的各個線程。那麼如何通過Java線程來執行的代碼呢?

Thread類中提供了一個最重要的run()方法,它被Thread類的start()方法調用,提供給我們線程要執行的代碼。爲了執行我們自己的代碼只需要重寫該方法即可。

方式一:繼承Thread類,重寫run()方法
創建Thread類的子類繼承該對象重寫run()方法,具體代碼實例如下:
public class MyThread extends Thread {
int count = 1, number;

public MyThread(int num) {
    number = num;
    System.out.println("創建線程 " + number);
}

public void run() {
    while (true) {
        System.out.println("線程 " + number + ":計數 " + count);
        if (++count == 6)
            return;
    }
}

public static void main(String args[]) {
    for (int i = 0; i < 5; i++)
        new MyThread(i + 1).start();
}

}
上述方法簡單明瞭符合大家的寫作習慣,但是有一個很大的缺點,那就是如果我們的類已經是其他類的子類,就無法再繼承Thread類,此時如果不想創建一個新的類,應該怎麼辦呢?

我們只能將我們的方法作爲參數傳遞給Thread類的實例,類似回調函數,但是Java是沒有指針的,我們只能傳遞一個包含該方法的實例。Java使用接口java.lang.Runnable解決這個問題。

Runnable接口只有一個run()方法,聲明類實現Runnable接口並提供這一方法,將線程代碼寫入這個方法完成任務,但是Runnable接口並沒有任何對線程的支持,因此必須創建Thread類的實例,通過Thread類的構造函數完成。

方式二:實現Runnable接口,代碼實例如下:
public class MyThread implements Runnable {
int count = 1, number;

public MyThread(int num) {
    number = num;
    System.out.println("創建線程 " + number);
}

public void run() {
    while (true) {
        System.out.println("線程 " + number + ":計數 " + count);
        if (++count == 6)
            return;
    }
}

public static void main(String args[]) {
    for (int i = 0; i < 5; i++)
        new Thread(new MyThread(i + 1)).start();
}

}
使用Runnable接口來實現多線程使得我們能夠在一個類中包容所有的代碼,有利於封裝。缺點在於只能使用一套代碼,若想創建多個線程並使各個線程執行不同的代碼,則仍必須額外創建類,如果這樣的話,在大多數情況下也許還不如直接用多個類分別繼承Thread來得方便。綜上所述比較兩種實現多線程的方法各有千秋,大家可以靈活運用。

下面和大家分析一下多線程在使用中的一些常見問題。
一:線程可以劃分爲七種狀態(有些人認爲只有五種,將鎖池和等待池看成阻塞狀態的特殊情況,這種認知也是正確的但是鎖池和等待池單獨分離有利於對程序的理解)。
1.初始狀態,線程創建,線程對象調用start()方法
2.可運行狀態,也就是等待CPU資源,等待運行的狀態
3.運行狀態,獲得了CPU資源,正在運行狀態
4.阻塞狀態,也就是讓出CPU資源,進入一種等待狀態,而且不是可運行狀態,有三種情況會進入阻塞狀態。
1)如等待輸入(輸入設備進行處理,而CUP不處理),則放入阻塞,直到輸入完畢,阻塞結束後會進入可運行狀態。
2)線程休眠,線程對象調用sleep()方法,阻塞結束後會進入可運行狀態。
3)線程對象 2 調用線程對象 1 的join()方法,那麼線程對象 2 進入阻塞狀態,直到線程對象 1 中止。
5.中止狀態,也就是執行結束
6.鎖池狀態
7.等待隊列

二:線程優先級
線程的優先級代表該線程的重要程度,當有多個線程同時處於可執行狀態並等待獲得CPU時間時,線程調度系統根據各個線程的優先級來決定給誰分配CPU時間,優先級高的線程有更大的機會獲得CPU時間,優先級低的線程獲取CPU時間機率比較小。調用Thread類的getPriority()和setPriority()方法來存取線程的優先級,線程的優先級界於1(MIN_PRIORITY)和10(MAX_PRIORITY)之間,缺省是5(NORM_PRIORITY)。

三:線程同步
由於同一進程的多個線程共享同一片存儲空間,在帶來方便的同時,也帶來了訪問衝突嚴重的問題。Java語言提供了專門的機制以解決這種衝突,有效避免了同一個數據對象被多個線程同時訪問。由於我們可以通過private關鍵字來保證數據對象只能被方法訪問,所以我們只需針對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩種用法:synchronized方法和synchronized塊。

  1. 通過在方法聲明中加入synchronized關鍵字來聲明synchronized方法。如:public synchronized void accessVal(int newVal);synchronized方法控制對類成員變量的訪問:每個類實例對應一把鎖,每個synchronized方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對於每一個類實例,其所有聲明爲synchronized的成員函數中至多隻有一個處於可執行狀態,從而有效避免了類成員變量的訪問衝突。在Java中不單單隻有類實例,每一個類也是對應一把鎖,這樣我們也可將類的靜態成員函數聲明爲synchronized,以控制其對類的靜態成員變量的訪問。

synchronized方法的缺陷:若將一個大的方法聲明爲synchronized將會大大影響效率,典型地,若將線程類的run()方法聲明爲synchronized,由於在線程的整個生命期內它一直在運行,因此將導致它對本類任何synchronized方法的調用都永遠不會成功。當然我們可以通過將訪問類成員變量的代碼放到專門的方法中將其聲明爲synchronized,並在主方法中調用來解決這一問題,但是Java爲我們提供了更好的解決辦法,那就是synchronized塊。

  1. 通過synchronized關鍵字來聲明synchronized塊,語法如下:
    synchronized(syncObject) {
    //允許訪問控制的代碼
    }
    synchronized塊是一個代碼塊,其中的代碼必須獲得對象syncObject(類實例或類)的鎖方能執行。由於可以針對任意代碼塊且可任意指定上鎖的對象,因此靈活性比較高。

四:線程阻塞
爲了解決對共享存儲區的訪問衝突,Java引入了同步機制。阻塞指的是暫停一個線程的執行以等待某個條件發生(如某資源就緒),Java提供了大量方法來支持阻塞,下面逐一進行分析:
1. sleep()方法:sleep()允許指定以毫秒爲單位的一段時間作爲參數,它使得線程在指定的時間內進入阻塞狀態,不能得到CPU時間,指定的時間一過,線程重新進入可執行狀態。

  1. suspend()和resume()方法:兩個方法配合使用,suspend()使得線程進入阻塞狀態不會自動恢復,必須其對應的resume()被調用,才能使得線程重新進入可執行狀態。

  2. yield()方法:yield()使得線程放棄當前分得的CPU時間,但是不使線程阻塞,即線程仍處於可執行狀態,隨時可能再次分得CPU時間。調用yield()的效果等價於調度程序認爲該線程已執行了足夠的時間從而轉到另一個線程。

  3. wait()和 notify()方法:兩個方法配合使用,wait()使得線程進入阻塞狀態,它有兩種形式,一種允許指定以毫秒爲單位的一段時間作爲參數,另一種沒有參數,前者當對應的notify()被調用或者超出指定時間時線程重新進入可執行狀態,後者則必須對應的notify()被調用。注意的是1 2 3 步驟阻塞時都不會釋放佔用的鎖而這一對方法則會釋放佔用的鎖。

關於 wait()和 notify()方法最後再說明兩點:
1)調用notify()方法導致解除阻塞的線程是因爲調用該對象的wait()方法而阻塞的線程中隨機選取的,我們無法預料哪一個線程將會被選擇,所以編程時要特別小心,避免因這種不確定性而產生問題。

2)除了notify(),還有一個方法notifyAll()也可起到類似作用,唯一的區別在於,調用notifyAll()方法將把因爲調用該對象的wait()方法而阻塞的所有線程一次性全部解除阻塞。當然,只有獲得鎖的那一個線程才能進入可執行狀態。

說到阻塞就不能不說一說死鎖的概念,略一分析就能發現suspend()方法和不指定超時期限的wait()方法的調用都可能產生死鎖。遺憾的是Java並不在語言級別上支持死鎖的避免,因此我們在編程中必須小心地避免死鎖。

五:守護線程
守護線程是一類特殊的線程,它和普通線程的區別在於它並不是應用程序的核心部分,當一個應用程序的所有非守護線程終止運行時,即使仍然有守護線程在運行,應用程序也將終止,相反只要有一個非守護線程在運行應用程序就不會終止。守護線程一般被用於在後臺爲其它線程提供服務。可以通過調用isDaemon()方法來判斷一個線程是否是守護線程,可以調用setDaemon()方法來將一個線程設爲守護線程。

六:線程組
線程組是Java特有的一個概念,在Java中線程組是ThreadGroup類的對象,每個線程都隸屬於唯一一個線程組,這個線程組在線程創建時指定並在線程的整個生命期內都不能更改。你可以通過調用包含 ThreadGroup 類型參數的 Thread 類構造函數來指定線程屬的線程組,若沒有指定則線程缺省的隸屬於名爲system的系統線程組。

在Java中除了預建的系統線程組外,所有線程組都必須顯式創建並且每個線程組又隸屬於另一個線程組。你可以在創建線程組時指定其所隸屬的線程組,若沒有指定則缺省的隸屬於系統線程組。這樣所有線程組組成了一棵以系統線程組爲根的樹。
Java線程組實例代碼,具體如下:
public class MyThreadGroup {

public static void main(String[] args) {
    ThreadGroup group = Thread.currentThread().getThreadGroup();
    System.out.println("-主線程組" + group.getName());
    System.out.println("--主線程組是否是後臺線程組" + group.isDaemon());
    new MyThread("---主線程組線程").start();
    ThreadGroup tg = new ThreadGroup("----新線程組");
    tg.setDaemon(true);
    System.out.println("-----tg線程組是否是後臺線程組" + tg.isDaemon());
    new MyThread(tg, "------tg組的線程一").start();
    new MyThread(tg, "-------tg組的線程二").start();
}

}

class MyThread extends Thread {

public MyThread(String name) {
    super(name);
}

public MyThread(ThreadGroup group, String name) {
    super(group, name);
}

public void run() {
    for (int i = 0; i < 10; i++) {
        System.out.println(getName() + "線程的i變量值爲" + i);
    }
}

}
Java允許我們對一個線程組中的所有線程同時進行操作,比如我們可以通過調用線程組的相應方法來設置其中所有線程的優先級,也可以啓動或阻塞其中的所有線程。

Java的線程組機制的另一個重要作用是線程安全。線程組機制允許我們通過分組來區分有不同安全特性的線程,對不同組的線程進行不同的處理,還可以通過線程組的分層結構來支持不對等安全措施的採用。Java 的 ThreadGroup 類提供了大量的方法來方便我們對線程組樹中的每一個線程組以及線程組中的每一個線程進行操作。

推薦↓↓↓↓↓↓
這裏寫圖片描述
更多推薦:微信公衆號《優哉遊哉》
關注微信公衆號“優哉遊哉”(w_z90110),回覆關鍵字領取資料:如Hadoop,Dubbo,CAS源碼等等,免費領取資料視頻和項目等。
微信公衆號涵蓋:程序人生、搞笑視頻、算法與數據結構、黑客技術與網絡安全、前端開發、Java、Python、Redis緩存、spring源碼、各大主流框架、Web開發、大數據技術、Storm、Hadoop、MapReduce、Spark、elasticsearch、單點登錄統一認證、分佈式框架、集羣、安卓開發、iOS開發、C/C++、.NET、Linux、MySQL、Oracle、NoSQL非關係型數據庫、運維等。

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