標籤:
學習Java中的線程時,自然而然地聯想到之前學過的作業系統中處理器那一塊的知識。
定義
文章開頭,先大概說一下程式、進程和線程的概念及其之間的關係。
程式:程式就是一段靜態代碼,或者一個可執行程式。
進程:進程是程式的一次動態執行的過程,它對應著從代碼載入、運行到結束的一次動態執行過程。
線程:比進程更小的執行單位,一個進程在執行過程中,可以產生多個線程,也就是多個分支。它是程式執行流的最小單位。
來看一個小程式:
public class Test {public static void main(String[] args) {fun1();}public static void fun1(){System.out.println(fun2()+fun3());}public static String fun2(){return "Hello ";}public static String fun3(){return "World!";}}
它的執行順序如下,在main方法中,從①執行到⑥是一條線,並沒有分支,這就是一個線程。
線程的實現
當JVM執行到main方法時,就會啟動一個線程,叫做主線程。如果main方法中還建立了其他線程,那麼JVM就會在主線程和其他線程之間輪流切換,保證每個線程都有機會使用CPU資源。
Java中的線程是通過java.lang.Thread類來實現的,每一個Thread對象都代表一個新的線程。
Java中實現線程有兩種方法:
1、繼承Thread類,並且重寫其run方法(用來封裝整個線程要執行的命令),調用start方法啟動線程。
比如下面這個類T要實現線程,則代碼如下:
public class CreateThreadTest{public static void main(String[] args) {T r=new T();r.start(); //T類的線程開始執行for(int i=0;i<100;i++){System.out.println("主線程正在執行~~~~"+i);}}}class T extends Thread{public void run(){//重寫父類中的run方法for(int i=0;i<100;i++){System.out.println("我建立的線程正在執行~~~~"+i);}}}
現在,這一個小程式一共有兩個線程正在執行,一個是主線程,還有一個是T類建立的線程。
2、實現Runnable介面
還有一種方法就是讓實現線程的類實現Runnable介面,實現Runnable介面中唯一的方法run(),然後把此類的執行個體當做Thread類的建構函式的參數,建立線程對象。
例如上面的例子,還可以這樣寫:
public class CreateThreadTest {public static void main(String[] args) {T r=new T();Thread t=new Thread(r); //建立線程對象t.start(); //T類的線程開始for(int i=0;i<100;i++){System.out.println("主線程正在執行~~~~"+i);}}}class T implements Runnable {public void run(){for(int i=0;i<100;i++){System.out.println("我建立的線程正在執行~~~~"+i);}}}
兩種方法的實質就是,都需要重寫run方法,最終都是由Thread類的start方法啟動線程。
溫馨提示:因為Java中不支援多繼承,所以實現線程時,一旦繼承了Thread類,就無法再繼承其他類了。但Java支援實現多個介面,所以推薦採用第二中方法,比較靈活。
多線程
多線程主要是為了同步完成多項任務,即同時執行多個線程。多線程把一個進程劃分為多個任務,它們彼此獨立地工作
我們都知道,現在大部分作業系統比如Windows、Mac OS X、Unix、Linux等,都是支援多線程的,但我們平時所說的多線程,並不意味著CPU在同時會處理多個線程,每個CPU在同一個時間點只會處理一個線程,只不過速度太快了,處理的時間極短,以至於我們可以認為它是在同一個時間段可以處理多個線程。所以只有當你的機器是“雙核”甚至“多核”時,才能實現真正意義上的多線程。
下面看一個多線程的例子:
兩個線程t1和t2的執行
public class ThreadTest {public static void main(String[] args) {Thread t1=new Thread(new T1());Thread t2=new Thread(new T2());t1.start();t2.start();}}class T1 implements Runnable{public void run(){for(int i=0;i<10;i++){System.out.println("線程1正在執行中------"+i);if(i==9){System.out.println("線程1執行已結束------"+i);break;}}}}class T2 implements Runnable{public void run(){for(int i=0;i<10;i++){System.out.println("線程2正在執行中------"+i);if(i==9){System.out.println("線程2執行已結束------"+i);break;}}}}
看下面的結果之前,先充分發揮你的大腦想一想到底結果應該是什麼樣子的~~
執行結果:
是不是跟您預測的不一樣呢?如果不加線程的話,本來的結果應該前十行都是線程1在執行,線程1執行完後線程2才開始執行,線程調度演算法使得每個線程執行一會進入等待狀態,再去執行另一個線程。
線程中常用的方法
如果親手嘗試過上面這個例子中,會發現這兩個線程t1和t2誰先執行,誰後執行,誰執行多長時間等等這些都是不確定、不可控的。下面就說一下線程中常用到的幾個方法。
★ void yield()方法
在一個線程中,如果執行到yield()方法,那麼這個線程就會讓出CPU資源,暫停當前正在執行的線程對象,轉而讓CPU去執行其他具有相同優先順序的線程。充分體現了線程樂于謙讓的精神!
例:i的值從0到99,每次輸出線程執行個體的名字,當i是10的倍數時,執行yield方法。
public class TestYield {public static void main(String[] args) {MyThread thread1=new MyThread("thread1");MyThread thread2=new MyThread("thread2");thread1.start();thread2.start();}}class MyThread extends Thread{MyThread(String name){super(name);}public void run(){for(int i=0;i<100;i++){System.out.println(getName()+":"+i);if(i%10==0){yield();}}}}
從結果中的前幾條輸出可以發現,對thread1來說,每當i是10的倍數時,進程就會讓出,thread2進程執行;thread2也是如此:
注意: yield()方法會將當前啟動並執行線程切換到可運行狀態,但可能沒有效果,因為實際中執行yield方法的線程還有可能被發送器再次選中。
★ void sleep(long millis)方法和void sleep(long millis,int nanos)
讓線程休眠(暫停執行)指定的時間長度,參數millis的單位是毫秒,參數nanos的單位是納秒。線程執行了sleep方法後就會進入阻塞狀態,指定的時間段過後,線程進入可執行狀態,等待被作業系統調度。
例:
import java.util.*;public class TestSleep {public static void main(String[] args){MyThread thread=new MyThread();thread.start();}}class MyThread extends Thread{public void run(){while(true){System.out.println("==="+new Date()+"===");try{sleep(1000);}catch(InterruptedException e){return;}}}}
看完代碼您應該猜到結果了,每隔一秒都會輸出目前時間,相當於一個定時器。這個程式一共有兩個線程,主線程(即main方法)中出了執行thread線程就沒有其他任務需要執行,所以這裡可以看做只執行thread這一個線程,如果把例子中的sleep方法去掉,那麼就會“唰唰唰”地不斷輸出目前時間(你的CPU風扇也會“唰唰唰”~~~~)。
★void join()方法
上面說線程執行了yield方法時,會自動讓出CPU資源,使狀態由運行狀態轉換為可運行狀態。join()方法可以說是恰恰相仿,當一個線程執行了join方法時,那麼它就會一直執行下去直到這個線程結束。
還是舉例來說明:
public class TestJoin { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new ThreadA()); Thread t2 = new Thread(new ThreadB()); t1.start(); t1.join(); // t1線程開始執行後,它將繼續執行下去,直到t1線程結束,否則絕不讓出CPU t2.start(); t2.join(); // t2線程開始執行後,它將繼續執行下去,直到t1線程結束,否則絕不讓出CPU } }class ThreadA implements Runnable { public void run() { for (int i=0;i<10;i++) { System.out.println("A線程正在運行~~~~" + i); } } } class ThreadB implements Runnable { public void run() { for (int i=0;i<10;i++) { System.out.println("B線程正在運行~~~~" + i); } } }
先來設想一下,加入t1和t2兩個線程啟動後不執行join方法,那麼CPU給它們分配的執行時間和順序就不一定相同,如左;如果t1和t2執行了join方法,那麼它們一旦開始執行,就將執行到底,如右。
為什麼要用多線程
最後一個問題,為什麼要用多線程?
在論壇裡看到一個大牛的比喻:單線程就是牛逼老闆從頭到尾一個人做完,另開一個線程就是老闆掰出一件事情叫一個小弟去做,這個小弟的進入會加快整個事情的進展,但有時可能會做起事來礙手礙腳。
當然,想要更深地理解多線程的精髓所在,光靠學這些理論還是不夠的,更重要的還是在實踐和項目中去挖掘和思考。
J2SE快速進階——Java多線程機制