單線程
任何程序至少有一個線程,即使你沒有主動地創建線程,程序從一開始執行就有一個默認的線程,被稱為主線程,只有一個線程的程序稱為單線程程序。如下面這一簡單的代碼,沒有顯示地創建一個線程,程序從main開始執行,main本身就是一個線程(主線程),單個線程從頭執行到尾。
public class Test{public static void main(String args[]) {System.out.println("1-100:");for (int i = 0; i < 100; i ++) {System.out.println(i + 1);}}
}
創建線程
單線程程序簡單明了,但有時無法滿足特定的需求。如一個文字處理的程序,我在打印文章的同時也要能對文字進行編輯,如果是單線程的程序則要等打印機打印完成之后你才能對文字進行編輯,但打印的過程一般比較漫長,這是我們無法容忍的。如果采用多線程,打印的時候可以單獨開一個線程去打印,主線程可以繼續進行文字編輯。在程序需要同時執行多個任務時,可以采用多線程。
在程序需要同時執行多個任務時,可以采用多線程。Java給多線程編程提供了內置的支持,提供了兩種創建線程方法:1.通過實現Runable接口;2.通過繼承Thread類。
Thread是JDK實現的對線程支持的類,Thread類本身實現了Runnable接口,所以Runnable是顯示創建線程必須實現的接口; Runnable只有一個run方法,所以不管通過哪種方式創建線程,都必須實現run方法。我們來看一個例子。
【Demo2】:線程的創建和使用
//通過實現Runnable方法
class ThreadA implements Runnable {private Thread thread;private String threadName;public ThreadA(String threadName) {thread = new Thread(this, threadName);this.threadName = threadName;}//實現run方法public void run() {for (int i = 0; i < 10; i ++) {System.out.println(threadName + ": " + i);}}public void start() {thread.start();}
}//通過繼承Thread類
class ThreadB extends Thread {private String threadName;public ThreadB(String threadName) {super(threadName);this.threadName = threadName;}//實現run方法public void run() {for (int i = 0; i < 10; i ++) {System.out.println(threadName + ": " + i);}}
}public class Test{public static void main(String args[]) {ThreadA threadA = new ThreadA("ThreadA");ThreadB threadB = new ThreadB("ThreadB");threadA.start();threadB.start();}
}
運行結果:
說明:上面的例子中例舉了兩種實現線程的方式。大部分情況下選擇實現Runnable接口的方式會優于繼承Thread的方式,因為:?
1. 從 Thread 類繼承會強加類層次;?
2. 有些類不能繼承Thread類,如要作為線程運行的類已經是某一個類的子類了,但Java只支持單繼承,所以不能再繼承Thread類了。--------------------------------------------------------------------------------------------------------------------------------------------
線程同步
線程與線程之間的關系,有幾種:
模型一:簡單的線程,多個線程同時執行,但各個線程處理的任務毫不相干,沒有數據和資源的共享,不會出現爭搶資源的情況。這種情況下不管有多少個線程同時執行都是安全的,其執行模型如下:?
圖 1:處理相互獨立的任務
型二:復雜的線程,多個線程共享相同的數據或資源,就會出現多個線程爭搶一個資源的情況。這時就容易造成數據的非預期(錯誤)處理,是線程不安全的,其模型如下:?
圖 2:多個線程共享相同的數據或資源
在出現模型二的情況時就要考慮線程的同步,確保線程的安全。Java中對線程同步的支持,最常見的方式是添加synchronized同步鎖。
我們通過一個例子來看一下線程同步的應用。
買火車票是大家春節回家最為關注的事情,我們就簡單模擬一下火車票的售票系統(為使程序簡單,我們就抽出最簡單的模型進行模擬):有500張從北京到上海的火車票,在8個窗口同時出售,保證系統的穩定性和數據的原子性。?
?
/*** 模擬服務器的類*/
class Service {private String ticketName; //票名private int totalCount; //總票數private int remaining; //剩余票數public Service(String ticketName, int totalCount) {this.ticketName = ticketName;this.totalCount = totalCount;this.remaining = totalCount;}public synchronized int saleTicket(int ticketNum) {if (remaining > 0) {remaining -= ticketNum;try { //暫停0.1秒,模擬真實系統中復雜計算所用的時間Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}if (remaining >= 0) {return remaining;} else {remaining += ticketNum;return -1;}}return -1;}public synchronized int getRemaining() {return remaining;}public String getTicketName() {return this.ticketName;}}/*** 售票程序*/
class TicketSaler implements Runnable {private String name;private Service service;public TicketSaler(String windowName, Service service) {this.name = windowName;this.service = service;}@Overridepublic void run() {while (service.getRemaining() > 0) {synchronized (this){System.out.print(Thread.currentThread().getName() + "出售第" + service.getRemaining() + "張票,");int remaining = service.saleTicket(1);if (remaining >= 0) {System.out.println("出票成功!剩余" + remaining + "張票.");} else {System.out.println("出票失敗!該票已售完。");}}}}
}
測試程序:
/*** 測試類*/
public class TicketingSystem {public static void main(String args[]) {Service service = new Service("北京-->上海", 500);TicketSaler ticketSaler = new TicketSaler("售票程序", service);//創建8個線程,以模擬8個窗口Thread threads[] = new Thread[8];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(ticketSaler, "窗口" + (i + 1));System.out.println("窗口" + (i + 1) + "開始出售 " + service.getTicketName() + " 的票...");threads[i].start();}}
}
結果如下:
窗口1開始出售 北京–>贛州 的票…?
窗口2開始出售 北京–>贛州 的票…?
窗口3開始出售 北京–>贛州 的票…?
窗口4開始出售 北京–>贛州 的票…?
窗口5開始出售 北京–>贛州 的票…?
窗口6開始出售 北京–>贛州 的票…?
窗口7開始出售 北京–>贛州 的票…?
窗口8開始出售 北京–>贛州 的票…?
窗口1出售第500張票,出票成功!剩余499張票.?
窗口1出售第499張票,出票成功!剩余498張票.?
窗口6出售第498張票,出票成功!剩余497張票.?
窗口6出售第497張票,出票成功!剩余496張票.?
窗口1出售第496張票,出票成功!剩余495張票.?
窗口1出售第495張票,出票成功!剩余494張票.?
窗口1出售第494張票,出票成功!剩余493張票.?
窗口2出售第493張票,出票成功!剩余492張票.?
窗口2出售第492張票,出票成功!剩余491張票.?
窗口2出售第491張票,出票成功!剩余490張票.?
窗口2出售第490張票,出票成功!剩余489張票.?
窗口2出售第489張票,出票成功!剩余488張票.?
窗口2出售第488張票,出票成功!剩余487張票.?
窗口6出售第487張票,出票成功!剩余486張票.?
窗口6出售第486張票,出票成功!剩余485張票.?
窗口3出售第485張票,出票成功!剩余484張票.?
……
在上面的例子中,涉及到數據的更改的Service類saleTicket方法和TicketSaler類run方法都用了synchronized同步鎖進行同步處理,以保證數據的準確性和原子性。
關于synchronized更詳細的用法請參見:《Java中Synchronized的用法》
線程控制
在多線程程序中,除了最重要的線程同步外,還有其它的線程控制,如線程的中斷、合并、優先級等。
線程等待(wait、notify、notifyAll)
Wait:使當前的線程處于等待狀態;?
Notify:喚醒其中一個等待線程;?
notifyAll:喚醒所有等待線程。
詳細用法參見:《?Java多線程中wait, notify and notifyAll的使用》
線程中斷(interrupt)
在Java提供的線程支持類Thread中,有三個用于線程中斷的方法:?
1、public void interrupt(); 中斷線程。?
2、public static boolean interrupted(); 是一個靜態方法,用于測試當前線程是否已經中斷,并將線程的中斷狀態 清除。所以如果線程已經中斷,調用兩次interrupted,第二次時會返回false,因為第一次返回true后會清除中斷狀態。?
3、public boolean isInterrupted(); 測試線程是否已經中斷。
【Demo4】:線程中斷的應用
/*** 打印線程*/
class Printer implements Runnable {public void run() {while (!Thread.currentThread().isInterrupted()) { //如果當前線程未被中斷,則執行打印工作System.out.println(Thread.currentThread().getName() + "打印中… …");}if (Thread.currentThread().isInterrupted()) {System.out.println("interrupted:" + Thread.interrupted()); //返回當前線程的狀態,并清除狀態System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted());}}
}
調用代碼:
Printer printer = new Printer();
Thread printerThread = new Thread(printer, "打印線程");
printerThread.start();
try {Thread.sleep(100);
} catch (InterruptedException e) {e.printStackTrace();
}
System.out.println("有緊急任務出現,需中斷打印線程.");
System.out.println("中斷前的狀態:" + printerThread.isInterrupted());
printerThread.interrupt(); // 中斷打印線程
System.out.println("中斷前的狀態:" + printerThread.isInterrupted());
結果:
打印線程打印中… …?
… …?
打印線程打印中… …?
有緊急任務出現,需中斷打印線程.?
打印線程打印中… …?
中斷前的狀態:false?
打印線程打印中… …?
中斷前的狀態:true?
interrupted:true?
isInterrupted:false
線程合并(join)
所謂合并,就是等待其它線程執行完,再執行當前線程,執行起來的效果就好像把其它線程合并到當前線程執行一樣。其執行關系如下:?
圖 4:線程合并的過程
public final void join()?
等待該線程終止public final void join(long millis);?
等待該線程終止的時間最長為 millis 毫秒。超時為 0 意味著要一直等下去。public final void join(long millis, int nanos)?
等待該線程終止的時間最長為 millis 毫秒 + nanos 納秒
這個常見的一個應用就是安裝程序,很多大的軟件都會包含多個插件,如果選擇完整安裝,則要等所有的插件都安裝完成才能結束,且插件與插件之間還可能會有依賴關系。
【Demo5】:線程合并
/*** 插件1*/
class Plugin1 implements Runnable {@Overridepublic void run() {System.out.println("插件1開始安裝.");System.out.println("安裝中...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("插件1完成安裝.");}
}/*** 插件2*/
class Plugin2 implements Runnable {@Overridepublic void run() {System.out.println("插件2開始安裝.");System.out.println("安裝中...");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("插件2完成安裝.");}
}
合并線程的調用:
System.out.println("主線程開啟...");
Thread thread1 = new Thread(new Plugin1());
Thread thread2 = new Thread(new Plugin2());
try {thread1.start(); //開始插件1的安裝thread1.join(); //等插件1的安裝線程結束thread2.start(); //再開始插件2的安裝thread2.join(); //等插件2的安裝線程結束,才能回到主線程
} catch (InterruptedException e) {e.printStackTrace();
}
System.out.println("主線程結束,程序安裝完成!");
結果如下:
主線程開啟…?
插件1開始安裝.?
安裝中…?
插件1完成安裝.?
插件2開始安裝.?
安裝中…?
插件2完成安裝.?
主線程結束,程序安裝完成!
優先級(Priority)
線程優先級是指獲得CPU資源的優先程序。優先級高的容易獲得CPU資源,優先級底的較難獲得CPU資源,表現出來的情況就是優先級越高執行的時間越多。
Java中通過getPriority和setPriority方法獲取和設置線程的優先級。Thread類提供了三個表示優先級的常量:MIN_PRIORITY優先級最低,為1;NORM_PRIORITY是正常的優先級;為5,MAX_PRIORITY優先級最高,為10。我們創建線程對象后,如果不顯示的設置優先級的話,默認為5。
【Demo】:線程優先級
/*** 優先級*/
class PriorityThread implements Runnable{@Overridepublic void run() {for (int i = 0; i < 1000; i ++) {System.out.println(Thread.currentThread().getName() + ": " + i);}}
}
調用代碼:
//創建三個線程
Thread thread1 = new Thread(new PriorityThread(), "Thread1");
Thread thread2 = new Thread(new PriorityThread(), "Thread2");
Thread thread3 = new Thread(new PriorityThread(), "Thread3");
//設置優先級
thread1.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(8);
//開始執行線程
thread3.start();
thread2.start();
thread1.start();
從結果中我們可以看到線程thread1明顯比線程thread3執行的快。
---------------------------------結束--------->僅供學習