一、程序與線程
1.1、任務調動
大部分作業系統(如Windows、Linux)的任務排程是采用時間片輪轉的搶占式排程方式,也就是說一個任務執行一小段時間後強制暫停去執行下一個任務,每個任務輪流執行。
多任務運作過程的示意圖如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2QvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc1TVupFcWJDZ2ZFSiZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TNyYjNyITMwIzMyYDM1EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
1.1作業系統中的任務排程
1.2 程序 我們都知道計算機的核心是CPU,它承擔了所有的計算任務;而作業系統是計算機的管理者,它負責任務的排程、資源的配置設定和管理,統領整個計算機硬體;應用程式側是具有某種功能的程式,程式是運作于作業系統之上的。
程序是一個具有一定獨立功能的程式在一個資料集上的一次動态執行的過程,是作業系統進行資源配置設定和排程的一個獨立機關,是應用程式運作的載體。程序是一種抽象的概念,從來沒有統一的标準定義。程序一般由程式、資料集合和程序控制塊三部分組成。程式用于描述程序要完成的功能,是控制程序執行的指令集;資料集合是程式在執行時所需要的資料和工作區;程式控制塊(Program Control Block,簡稱PCB),包含程序的描述資訊和控制資訊,是程序存在的唯一标志。
程序具有的特征:
動态性:程序是程式的一次執行過程,是臨時的,有生命期的,是動态産生,動态消亡的;
并發性:任何程序都可以同其他程序一起并發執行;
獨立性:程序是系統進行資源配置設定和排程的一個獨立機關;
結構性:程序由程式、資料和程序控制塊三部分組成。
1.3 線程
在早期的作業系統中并沒有線程的概念,程序是能擁有資源和獨立運作的最小機關,也是程式執行的最小機關。任務排程采用的是時間片輪轉的搶占式排程方式,而程序是任務排程的最小機關,每個程序有各自獨立的一塊記憶體,使得各個程序之間記憶體位址互相隔離。
後來,随着計算機的發展,對CPU的要求越來越高,程序之間的切換開銷較大,已經無法滿足越來越複雜的程式的要求了。于是就發明了線程,線程是程式執行中一個單一的順序控制流程,是程式執行流的最小單元,是處理器排程和分派的基本機關。一個程序可以有一個或多個線程,各個線程之間共享程式的記憶體空間(也就是所在程序的記憶體空間)。一個标準的線程由線程ID、目前指令指針(PC)、寄存器和堆棧組成。而程序由記憶體空間(代碼、資料、程序空間、打開的檔案)和一個或多個線程組成。
1.4 程序與線程的差別
1. 線程是程式執行的最小機關,而程序是作業系統配置設定資源的最小機關;
2.一個程序由一個或多個線程組成,線程是一個程序中代碼的不同執行路線;
3.程序之間互相獨立,但同一程序下的各個線程之間共享程式的記憶體空間(包括代碼段、資料集、堆等)及一些程序級的資源(如打開檔案和信号),某程序内的線程在其它程序不可見;
4.排程和切換:線程上下文切換比程序上下文切換要快得多。
線程與程序關系的示意圖:
程序與線程的資源共享關系
單線程與多線程的關系
二、Java中的線程
2.1 線程的概述(Introduction)
線程是一個程式的多個執行路徑,執行排程的機關,依托于程序存在。 線程不僅可以共享程序的記憶體,而且還擁有一個屬于自己的記憶體空間,這段記憶體空間也叫做線程棧,是在建立線程時由系統配置設定的,主要用來儲存線程内部所使用的資料,如線程執行函數中所定義的變量。
注意:Java中的多線程是一種搶占機制而不是分時機制。搶占機制指的是有多個線程處于可運作狀态,但是隻允許一個線程在運作,他們通過競争的方式搶占CPU。
線程的定義(Defining)
2.2 線程的定義(Defining)
定義一個線程(Defining a Thread)有兩種方法
1) 繼承java.lang.Thread類
2) 實作java.lang.Runnable接口
1) 繼承java.lang.Thread類
/**
* 使用繼承java.lang.Thread類的方式建立一個線程
*
*
*/
public class ThreadTest extends Thread {
/**
* 重寫(Override)run()方法 JVM會自動調用該方法
*/
public void run() {
System.out.println("I'm running!");
}
}
注意:重寫(override)run()方法在該線程的start()方法被調用後,JVM會自動調用run方法來執行任務;但是重載(overload)run()方法,該方法和普通的成員方法一樣,并不會因調用該線程的start()方法而被JVM自動運作。 例如:
public class ThreadTest1 extends Thread {
/**
* 重寫(Override)run()方法 JVM會自動調用該方法
*/
@Override
public void run() {
System.out.println("I'm running!");
}
/**
* 重載(Overload)run()方法 和普通的方法一樣,并不會在該線程的start()方法被調用後被JVM自動運作
*/
public void run(int times) {
System.out.println("I'm running!(Overload)");
}
}
不建議使用此方法定義線程,因為采用繼承Thread的方式定義線程後,你不能在繼承其他的類了,導緻程式的可擴充性大大降低。
2) 實作java.lang.Runnable接口
/**
* 通過實作Runnable接口建立一個線程
* @author DreamSea
*/
public class ThreadTest2 implements Runnable {
public void run() {
System.out.println("I'm running!");
}
}
2.3 線程的啟動
何一個線程的執行的前提都是必須有Thread class的執行個體存在,并且通過調用run()方法啟動線程。
1)如果線程是繼承Thread類,則建立方式如下:
ThreadTest1 tt = new ThreadTest1();
tt.start();
2)如果是實作Runnable接口,則建立方式如下:
ThreadTest2 tt = new ThreadTest2();
Thread t = new Thread(tt);
t.start();
2.4 線程的狀态
線程狀态圖
新生狀态(New): 當一個線程的執行個體被建立即使用new關鍵字和Thread類或其子類建立一個線程對象後,此時該線程處于新生(new)狀态,處于新生狀态的線程有自己的記憶體空間,但該線程并沒有運作,此時線程還不是活着的(not alive);
就緒狀态(Runnable): 通過調用線程執行個體的start()方法來啟動線程使線程進入就緒狀态(runnable);處于就緒狀态的線程已經具備了運作條件,但還沒有被配置設定到CPU即不一定會被立即執行,此時處于線程就緒隊列,等待系統為其配置設定CPCU,等待狀态并不是執行狀态; 此時線程是活着的(alive);
運作狀态(Running): 一旦擷取CPU(被JVM選中),線程就進入運作(running)狀态,線程的run()方法才開始被執行;在運作狀态的線程執行自己的run()方法中的操作,直到調用其他的方法而終止、或者等待某種資源而阻塞、或者完成任務而死亡;如果在給定的時間片内沒有執行結束,就會被系統給換下來回到線程的等待狀态;此時線程是活着的(alive);
阻塞狀态(Blocked):通過調用join()、sleep()、wait()或者資源被暫用使線程處于阻塞(blocked)狀态;處于Blocking狀态的線程仍然是活着的(alive)
死亡狀态(Dead):當一個線程的run()方法運作完畢或被中斷或被異常退出,該線程到達死亡(dead)狀态。此時可能仍然存在一個該Thread的執行個體對象,當該Thready已經不可能在被作為一個可被獨立執行的線程對待了,線程的獨立的call stack已經被dissolved。一旦某一線程進入Dead狀态,他就再也不能進入一個獨立線程的生命周期了。對于一個處于Dead狀态的線程調用start()方法,會出現一個運作期(runtime exception)的異常;處于Dead狀态的線程不是活着的(not alive)。
2.5 線程的方法(Method)、屬性(Property)
1)優先級(priority)
每個類都有自己的優先級,一般property用1-10的整數表示,預設優先級是5,優先級最高是10;優先級高的線程并不一定比優先級低的線程執行的機會高,隻是執行的機率高;預設一個線程的優先級和建立他的線程優先級相同;
2)Thread.sleep()/sleep(long millis)
目前線程睡眠/millis的時間(millis指定睡眠時間是其最小的不執行時間,因為sleep(millis)休眠到達後,無法保證會被JVM立即排程);sleep()是一個靜态方法(static method) ,是以他不會停止其他的線程也處于休眠狀态;線程sleep()時不會失去擁有的對象鎖。 作用:保持對象鎖,讓出CPU,調用目的是不讓目前線程獨自霸占該程序所擷取的CPU資源,以留一定的時間給其他線程執行的機會;
3)Thread.yield()
讓出CPU的使用權,給其他線程執行機會、讓同等優先權的線程運作(但并不保證目前線程會被JVM再次排程、使該線程重新進入Running狀态),如果沒有同等優先權的線程,那麼yield()方法将不會起作用。
4)thread.join()
使用該方法的線程會在此之間執行完畢後再往下繼續執行。
5)object.wait()
當一個線程執行到wait()方法時,他就進入到一個和該對象相關的等待池(Waiting Pool)中,同時失去了對象的機鎖—暫時的,wait後還要返還對象鎖。目前線程必須擁有目前對象的鎖,如果目前線程不是此鎖的擁有者,會抛出IllegalMonitorStateException異常,是以wait()必須在synchronized block中調用。
6)object.notify()/notifyAll()
喚醒在目前對象等待池中等待的第一個線程/所有線程。notify()/notifyAll()也必須擁有相同對象鎖,否則也會抛出IllegalMonitorStateException異常。
7)Synchronizing Block
Synchronized Block/方法控制對類成員變量的通路;Java中的每一個對象都有唯一的一個内置的鎖,每個Synchronized Block/方法隻有持有調用該方法被鎖定對象的鎖才可以通路,否則所屬線程阻塞;機鎖具有獨占性、一旦被一個Thread持有,其他的Thread就不能再擁有(不能通路其他同步方法),方法一旦執行,就獨占該鎖,直到從該方法傳回時才将鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀态。