Java 里的thread (线程)简介
在Java里 thread 就是線程的意思.
說到線程的概念, 自然離不開另外兩個詞: 程序和進程.
從最基本的程序講起:
一. 什么是程序(Program)
所謂程序, 就是1個嚴格有序的指令集合. 程序規定了完成某一任務時,計算機所需要做的各種操作, 以及操作的順序.
1.1 單道程序運行環境
所謂單道程序環境就是指, 計算機除了操作系統之外, 只允許運行1個用戶程序.
以前的DOS系統就是1個典型的單道程序運行環境.
單道程序有如下特點:
1. 資源的獨占性: 任何時候, 內存內只有1個用戶程序, 這個程序獨享系統的所有資源.
2. 執行順序性: 內存中只執行1個程序, 各程序是按次序執行的, 即是做完1件事后, 才做另一件事.
??????????? 絕不可能執行1個程序的中途暫停, 然后去執行另1個程序.
3.結果的再現性: 只要執行環境和初始條件相同, 重復執行1個程序, 獲得的結果總是一樣的.
1.2 多道程序運行環境
所謂多道程序運行環境是指, 計算機除了操作系統之外, 允許同時運行多個用戶程序.
當今的unix, linux都是典型的多道程序運行環境.
多道程序有如下特點:
1. 間斷性: 也就是cpu會在多個程序之間切換執行, 例如cpu執行程序A一段時間后, 會切換去執行程序B一段時間.
????????????????? 由于這個時間段很短, 所以對用戶造成1個程序A和程序B同時執行的假象.
2. 失去封閉性:? 程序執行受外界影響, 也就是多個程序之間共享資源, 互相制約.
3. 不可再現性:? 重復執行時, 可能得到不同的結果. 原因就是上面的點, 執行時可能會受到內存中其他程序的影響.
二. 什么是進程(process)
進程這個概念是基于多道程序運行環境來講的.?
1個進程就是程序在內存中運行的1個實例
由于在多道程序運行環境中, 同1個程序可以同時產生兩個實例在內存里運行.
舉個簡單例子:? 例如你可以在操作系統打開兩個gvim 文本編輯器, 同時編輯兩個不同的文件.
這時執行ps -ef | grep gvim 就會見到系統中有兩個名字是gvim的進程, 但是它們的進程id是不同的.
也就是將,? gvim這個程序在內存中生成兩個進程同時運行.
在多道程序運行環境中.? 引用進程這個概念來描敘程序在內存里事例, 用來區別于程序本身.
真正深入了解進程并不簡單, 進程其實算是屬于操作系統這門課的范疇. 1個進程包括程序段, 數據段, 程序控制塊等,? 但是作為一個普通的程序猿, 沒必要理解得這么深入.
三. 什么是線程(thread).
相對地, 我認為作為1個普通的程序猿, 必須深入了解線程這個概念.
其實, 線程就是1個進程的執行路徑.
一般的java程序都是從啟動類的main函數入口開始執行, main函數的結束而停止.?? 這條執行路徑就是java程序的主線程.
也就是講:
線程是相對于進程來講的, 1個進程至少存在1條線程.
而java允許多線程編程, 也就是1個進程除主線程外,? 還允許存在1個或若干個其他線程, cpu會在這些線程當中間斷切換執行, 給用戶造成同時執行的假象.
四. 1個單線程java程序的簡單例子.
例子如下:
package Thread_kng;class S_thrd_1{public void f(){while (true){System.out.printf("Thread main is runing!\n");}//System.out.printf("f() is done!\n"); //compilation fail} }public class S_thread_expl{public static void g(){S_thrd_1 s = new S_thrd_1();s.f(); System.out.printf("g() is done!\n");} }上面的例子中,? g() 函數調用了f() 函數,
g()函數在最后嘗試輸出"g() is done!" , 但是因為f() 是一個無限循環.? 所以g() 調用f()后, 根本沒機會執行后面的語句!
也就是說, 雖然g() 跟 f() 是兩個不同類的函數, 但是它們是在程序中的同一個線程(主線程)內執行.???
在同一個線程內, 語句總是按程序猿編寫的順序依次執行, 一條語句未執行完時, 不會跳到后面執行其他的語句.
五. 1個多線程java程序的簡單例子.
例子如下:
package Thread_kng;class M_thrd_1 extends Thread{ //extendspublic M_thrd_1(String name){super(name);}public void run(){ //overwrite the method run() of superclasswhile (true){System.out.printf("Thread " + this.getName()+ " is runing!\n");}//System.out.printf("f() is done!\n"); //compilation fail} }public class M_thread_expl{public static void g(){M_thrd_1 s = new M_thrd_1("T1");s.start(); //start()method is extended from superclass, it will call the method run()M_thrd_1 s2 = new M_thrd_1("T2");s2.start();System.out.printf("g() is done!\n");}
上面例子中, 類 M_thrd_1繼承了線程類Thread.? 并重寫了run方法.
在下面的g()函數中.
實例化了兩個類M_thrd_1的對象s, s1 的對象.
執行了start()方法.
start()方法繼承字Thread, 它會啟動1新線程, 并調用run()函數.
而g()后面本身也在不斷循環輸出"THread Main is running"
所以輸出如下:
可以 見到, 輸出結果是T1,T2 和Main 交替輸出在屏幕上.
實際上, 這個程序有3個線程.
其中1個就是主線程.
主線程main通過實例化兩個M_thrd_1的對象, 調用其start()函數開啟了兩個子線程.
其中1個子線程不斷輸出T1
另1個子線程不斷輸出T2
但是主線程并不會等待其子線程執行完成, 會繼續執行下面的代碼,所以不斷循環輸出Main!
所以這個例子實際上要3個線程在輸出信息到屏幕了!
六. Java 開啟一條新進程的兩種方式.
java開啟一條新線程, 有兩種方式. 但是都要利用java的線程基類Thread.
6.1 方式1. 創建Thread的派生類
這個就類似上面的例子:
具體步驟如下:
6.1.1 線程準備: 新建1個類, 繼承線程類Thread.? 將業務代碼寫入重寫后的run() 方法中.
例如上面的例子中的M_thrd_1類
class M_thrd_1 extends Thread{ //extendspublic M_thrd_1(String name){super(name);}public void run(){ //overwrite the method run() of superclasswhile (true){System.out.printf("Thread " + this.getName()+ " is runing!\n");}//System.out.printf("f() is done!\n"); //compilation fail} }可以見到, 我們將線程要執行的業務代碼寫入了run() 方法.
6.1.2 啟動線程: 新建1個上述類的對象, 運行start()方法
例如
M_thrd_1 s = new M_thrd();
s. start();
其中start() 是類M_thrd_1 繼承自超類Thread的.
我們看看JDK API 對start() 方法的定義:
?void? start()
??????????使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
可以見到, 調用了start() 方法后, 會開始執行改線程, 也就是在當前線程下開啟一條新線程.
而這條新線程做什么呢, 就是執行run()里面的代碼.
所以我們一般只需要把也業務的代碼寫入run()里面就ok了. 不要重寫start()方法.
注意, 如果直接執行s.run(); 則會在當前線程下執行run里面的代碼, 并沒有開啟一條新線程. 區別很大的.
6.2 方式2. 創建1個類, 實現Runnable 接口
步驟跟上面的方式差別不大
6.2.1 線程準備: 新建1個類, 實現接口Runnable. 將業務代碼寫入重寫后的run() 方法中.
例子:
class M_thrd_2 implements Runnable{ //extendspublic int id = 0;public void run(){ //overwrite the method run() of superclasswhile (true){System.out.printf("%s: Thread " + this.id+ " is runing!\n", Thread.currentThread().getName());}//System.out.printf("f() is done!\n"); //compilation fail} }上面的例子 M_thrd_2 就實現了接口Runnable
并重寫了接口Runnable 的抽象方法run();
注意.? currentThread() 是類Thread的一個靜態方法, 作用是獲取當前語句所在的線程.
6.2.2 啟動線程: 新建1個上述類的對象s, 在新建1個類Thread的對象t, 把s作為一個參數用于t的構造方法.? 并執行t.start()
貌似比方式一復雜.
其實非如下:
new Thread(new M_thrd_2()).start()
例子:
public class M_thread_expl2{public static void g(){M_thrd_2 s = new M_thrd_2();s.id = 1;Thread t1 = new Thread(s);t1.start();Thread t2 = new Thread(s);s.id = 2;t2.start();} }在g() 函數中,
首先新建1個類M_thrd_2的 對象s.
并利用s作為參數 建立了兩個線程類Thread的對象t1 和 t2. 并啟動這兩個線程.
注:
1. 這個例子中有3個線程, 其中1個主線程(也就是g() 函數所在的線程). 開啟了兩個子線程, 該兩個子線程都在不斷循環輸出信息到屏幕上.
2. Thread(object ob) 是1個屬于類Thread的構造方法,? 其中的對象ob 必須實現Runnable 接口.
3. 這個例子執行時 , 第一個t1首先會輸出id = 1,??? 但是當第二個線程t2開始執行后, t1會輸出id=2,? 因為t1, 和t2都是利用同1個對象建立的.
??? 也就是說, 這個對象的變動會同時影響兩個線程.
輸出如下:
6.3 兩種方式的區別
1.? 方式1是創建繼承類Thread的派生類,? 方式2是創建實現Runnable接口的類.
2.? 啟動方式:? 方式1是直接調用業務類的對象的start()方法, 方式2是利用業務類類的對象新建1個類Thread的對象, 并調用該對象的start()方法.
3.? 如果要啟動多個線程, 通過方式1需要新建多個不同業務類的對象, 方式2 則可以通過業務1個對象 構建多個Thread類對象, 但是業務對象的變動會同時影響對應的多個線程.
七. 關于線程的一些常用方法介紹.
7.1 run()
我們要吧線把要執行的業務代碼寫入這個方法. 上面提過了.? 注意, 如果我們直接執行1個線程對象的run()方法可以合法的, 但是仍然是在當前線程內執行, 并沒有開始一條新線程.
7.2 Start()
當1個線程or其派生類執行1個start()方法. 就開啟1個新線程, 并調用該類的run()方法.
注意: 1個線程如果已經調用過一次start(), 再調用start()則或拋異常.
7.3 stop()
停止1個1個線程.
7.4 setName() 和 getName()
設置和獲得對應線程的名字.
7.5 Thread.currentThread()
注意這個方法是一個static方法,? 而上面的都不是靜態方法.
這個方法返回當前執行語句所屬的線程.
例如1個線程對象A.??? 它的run() 方法里調用了 Thread.currentThread().getName() 函數.
假如直接執行A.run()? 則返回的是當前線程(而不是A線程) 的名字,? 因為對象A并沒有啟動自己的線程.
假如執行難過A.start(), 那么該函數就返回A啟動的線程的名字了.
八, 小結
這篇文章只是簡介, 很多重要的方法例如 sleep() , wait() 等都沒有介紹, 這些是線程控制的內容. 本吊打算在另一篇博文里介紹.
總結
以上是生活随笔為你收集整理的Java 里的thread (线程)简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jar包的生成和使用简单例子
- 下一篇: Java里的线程控制