詳細文檔:
Java中的進程與線程.rar | 474KB | 1/7/2017 6:21:15 PM |
概述:
幾乎任何的操作系統都支持運行多個任務,通常一個任務就是一個程序,而一個程序就是一個進程。當一個進程運行時,內部可能包括多個順序執行流,每個順序執行流就是一個線程。
進程與線程:
進程是指處於運行過程中的程序,並且具有一定的獨立功能。進程是系統進行資源分配和調度的一個單位。當程序進入內存運行時,即為線程。
進程擁有以下三個特點:
1:獨立性:進程是系統中獨立存在的實體,它可以獨立擁有資源,每一個進程都有自己獨立的地址空間,沒有進程本身的運行,用戶進程不可以直接訪問其他進程的地址空間。
2:動態性:進程和程序的區別在於進程是動態的,進程中有時間的概念,進程具有自己的生命周期和各種不同的狀態。
3:並發性:多個進程可以在單個處理器上並發執行,互不影響。
並發性和並行性是不同的概念:並行是指同一時刻,多個命令在多個處理器上同時執行;並發是指在同一時刻,只有一條命令是在處理器上執行的,但多個進程命令被快速輪換執行,使得在宏觀上具有多個進程同時執行的效果。
對於一個CPU而言:只能在某一時間點執行一個程序。
多進程的並發策略有:共用式的多任務操作策略(WIN3.1和Mac OS9),現在操作系統大多采用效率更高的搶占式多任務操作策略(Windows NT、Windows 2000以及UNIX/Linux)等操作系統。

package Test; public class FirstThread extends Thread{ private int i; @Override public void run() { for(;i<10;i++) { System.out.println(getName()+"\t"+i); } } public static void main(String[] args) { for (int i = 0; i <10; i++) { System.out.println(Thread.currentThread().getName()+"\t"+i); if(i==5) { FirstThread f1=new FirstThread(); FirstThread f2=new FirstThread(); f1.start(); f2.start(); } } } }

package Test; public class SecondThread implements Runnable { private int i; @Override public void run() { for (; i < 20; i++) { System.out.println(Thread.currentThread().getName()+" "+i); if(i==20) { System.out.println(Thread.currentThread().getName()+"執行完畢"); } } } public static void main(String[] args) { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+" "+i); if(i==5) { SecondThread s1=new SecondThread(); Thread t1=new Thread(s1,"線程1"); Thread t2=new Thread(s1,"線程2"); t1.start(); try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } t2.start(); } } } }
由於線程的不穩定性,可能同時出現線程1和線程2
采用Ruunable接口的方式創建多個線程可以共享線程類的實例變量,這是因為在這種方式下,程序創建的Runnable對象只是線程的target,而多個線程可以共享一個target,所以多個線程可以共享一個實例變量!
使用callable和future創建線程
通過Runnable實現多線程其實就是將run包裝成線程的執行體,但是目前java無法將任意方法包裝成線程執行體
從Java5開始,Java提供 Callable接口,Callable接口提供了一個call()方法可以作為線程執行體,看起來和Runnable很像,但call()方法更強大——call()方法可以有返回值、call()方法可以拋出異常
Java5提供了Future接口來代表Callable接口的call()方法的返回值,並未Future接口提供了一個FutureTask實現類,該實現類實現類Future接口,也實現了Runnable接口——可以作為Thread的target。
使用該方法創建有返回值的線程的步驟如下:
1:創建Callable接口的實現類,並實現call方法,該call方法會成為線程執行體,且call方法具有返回值,在創建callable接口的實現類!
2:使用FutrueTask類來包裝Callable對象,該FutrueTask封裝類Callable的call方法的返回值
3:使用FutrueTask對象作為Thread的target創建並啟動新線程!
4:使用FutrueTask的get方法獲取執行結束后的返回值

package Test; import java.util.concurrent.Callable; public class target implements Callable<Integer> { int i=0; @Override public Integer call() throws Exception { for (; i < 20; i++) { System.out.println(Thread.currentThread().getName()+""+i); } return i; } } package Test; import java.util.concurrent.FutureTask; public class ThridThread { public static void main(String[] args) { target t1=new target(); FutureTask<Integer> ft=new FutureTask<Integer>(t1); Thread t2=new Thread(ft,"新線程"); t2.start(); try { System.out.println(ft.get()); } catch (Exception e) { // TODO: handle exception } } }
采取Runnable、Callable的優勢在於——線程類只是實現了Runnable或Callable接口,還可以繼承其它類;在這種方法下,多個線程可以共享一個target對象,因此非常適合多個相同線程處理同一份資源的情況,從而將CPU、代碼和數據分開,形參清晰的模型,體現了面對對象的編程思想。劣勢在於編程復雜度略高。
線程的生命周期:
當線程被創建並被啟動時,它既不是一啟動就進入了執行狀態,在線程的生命周期中,它要經過new(新建),就緒(Runnable),運行(Running),阻塞(Blocked),dead(死亡)。
當線程啟動之后,它不可能一直霸占着cpu獨自運行,所有cpu需要在多條線程輪流切換,於是線程就也會多次在運行.就緒之間切換。
新建和就緒狀態
當程序使用new關鍵字創建了一個線程時,該線程就處於新建狀態,此時它和其它java對象一樣,僅有虛擬機分配內存,並初始化成員變量的值。此時的線程對象並沒有表現出線程的任何動態特征,程序也不會執行線程的線程執行體。
當線程對象調用了start()方法后,該線程就處於就緒狀態,java虛擬機會為其創建方法調用棧和程序計數器,處於該狀態的線程並沒有開始執行,只是表明該線程可以運行了,至於該線程何時運行,取決於JVM的調度。
啟動線程要調用start方法,而不是run方法,永遠不要調用線程的run方法,如果調用run方法,系統會把線程對象當作普通的對象,會吧線程的執行體當作普通方法來調用!
在調用了run方法之后,該線程就不在處於新建狀態,不要再調用該線程的start方法!
java中只能對處於新建狀態的線程使用start方法,否則將會引發IllegalThreadStateException異常!
如果希望調用子線程的start()方法后子線程立即開始執行,可以使用Thread.sleep(1)來讓當前運行的線程(主線程)睡眠一毫秒,這樣CPU就會立即啟動另一個處於就緒狀態的線程,需要注意的是,使用Thread.sleep()方法需要聲明InterruptedException異常!
運行和阻塞狀態:
當發生如下的幾種情況時,將會進入阻塞狀態:
當線程調用sleep方法主動放棄所占用的處理器資源
線程調用了一個阻塞時的IO方法,在該方法返回之前,線程會被阻塞
線程試圖獲得一個同步監視器,但該同步監視器正被其他線程鎖持有
線程正在等待某個通知(notify)
程序調用了線程的suspend方法將該線程掛起
當以上幾個情況,當發生如下的情況將會重新進入就緒狀態
線程從阻塞狀態只能進入就緒狀態,無法直接進入運行狀態。就緒和運行狀態之間的轉換通常不受程序控制,而是系統線程的調度決定的。
調用yield()方法可以讓處於運行時的線程轉入就緒狀態。
線程死亡:
線程會以以下三種方式結束,結束后處於死亡狀態
run或call方法執行完成,程序結束
線程拋出一個未捕獲的Exception或者Error
直接調用該線程的stop方法來結束線程
當主線程結束時,其它線程不受任何影響,並不會隨之結束。一旦子線程啟動起來后,他就會擁有和主線程相同的地位,它不會受主線程影響。
為了測試某個線程是否死亡,可以調用該線程的isAlive方法,當線程處於就緒,運行,阻塞三種狀態時,將返回true;當線程處於新建,死亡兩種狀態時返回為false。
不要試圖對一個已經死亡的線程調用start方法讓它重新啟動,死亡后的線程無法作為線程使用。
如果處於非新建狀態的線程使用start方法,就會引發IllegalThreadStateException異常。
控制線程:
join線程:
Thread提供了讓一個線程等待另一個線程完成的方法--join方法,當在某個程序執行流中調用其他線程的join方法,調用線程將被阻塞,直到被join方法加入的join線程執行完畢為止。
join方法通常由使用線程的程序調用,以將大問題划為許多小問題,每個小問題分配一個線程,當所有的小問題都被處理之后,再調用主線程進行下一步操作!

package Test1; public class JoinThread extends Thread{ public JoinThread(String name) { super(name); } @Override public void run() { for (int i = 0; i <10; i++) { System.out.println(getName()+"\t"+i); } } } package Test1; public class Test { public static void main(String[] args) throws InterruptedException { new JoinThread("新線程").start(); for (int i = 0; i <10; i++) { if(i==5) { JoinThread j1=new JoinThread("被join的線程"); j1.start(); j1.join(); } System.out.println(Thread.currentThread().getName()+" "+i); } } }
在被join的線程執行前,兩個線程交替執行,而主線程處於等待狀態,直到被join的線程執行完畢,主線程繼續執行!
后台線程:
有一種線程,是在后台運行的,其任務是為其他線程提供服務,這種線程稱之為后台線程(Daemon Thread),又稱之為守護線程。jvm的垃圾回收器就是典型的后台進程。
當前台線程全部死亡,后台線程會自動死亡
調用Thread的setDaemon(ture)方法可以將指定線程設置成為后台線程。
當整個虛擬機只剩下后台線程時,程序就沒有運行的必要了,所有虛擬機將退出
Thread類還提供了一個isDaemon方法,用於指定該線程是否是后台線程!
前台創建的線程默認為前台線程,而后台創建的線程默認為后台線程。
前台線程死亡時,jvm會通知后台線程死亡,但它從接受指令到做出響應需要一段時間 此外,如果要將某個線程設置為后台線程,必須要在該線程啟動之前設置
,也就是setDaemon(true)必須在start方法之前調用,否則會引發IllegalThreadStateException異常。
線程睡眠:sleep
當前線程調用sleep方法進入阻塞狀態時,在其睡眠時間內,該線程不會獲得執行的機會
即便系統中沒有其它可執行的線程,處於sleep的線程也不會執行,因此sleep方法常用於暫停程序的執行!
線程讓步:
yield會讓該線程暫停,但是它不會阻塞線程,其只是將線程轉入就緒狀態,也就是說,yield方法只是讓當前線程暫停一下,讓系統的線程調度器重新調度一次,完全可能出現這種情況,--某個線程調用了yield后,線程調度器又將其調用出來執行
在多CPU並行的環境下,yield功能有時並不明顯
sleep()方法和yield方法的區別:
sleep方法暫停當前線程后會給其它線程執行機會,不會理會其它線程的優先級,但yield方法之后給優先級相同,或優先級更高的線程執行機會。
sleep方法會將線程轉入阻塞狀態,直到經過阻塞時間才會轉為就緒狀態,而yield方法不會轉入阻塞狀態,只是強制將當前線程轉入就緒狀態
sleep方法聲明拋出了InterruptedException異常,所有調用sleep方法就要捕獲此異常,而yield方法則沒有
sleep方法比yield方法有更好的執行!
改變線程的優先級:
每個線程都有一定的優先級,優先級更高的線程將會有更多的執行機會
每個線程默認的優先級都與創建它的父進程的優先級相同,默認情況下,main進程具有普通優先級
Thread類提供setPriority(int newPriority)和getPriority()方法來設置和返回線程的優先級其中setPriority參數是int類型,范圍0到10之間
Thread類有三個靜態常量:MAX_PRIORITY :10 MIN_PRIORITY :1 NORM_PRIORITY:5
線程同步:
同步代碼塊:
synchronize(obj){ }
obj:同步監視器,含義:線程開始執行同步代碼塊時,必須先獲得對同步監聽器的鎖定

package Test2; public class Account { private String AccountNo; private double balance; public Account(){ } public Account(String accountNo, double balance) { AccountNo = accountNo; this.balance = balance; } public String getAccountNo() { return AccountNo; } public double getBalance() { return balance; } public void setAccountNo(String accountNo) { AccountNo = accountNo; } public void setBalance(double balance) { this.balance = balance; } public synchronized void draw(double drawcount) { if(balance>=drawcount) { System.out.println(Thread.currentThread().getName()+"取款成功,取出"+drawcount+"元"); try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } balance-=drawcount; System.out.println("余額:"+balance); } else { System.out.println(Thread.currentThread().getName()+" "+"余額不足"); } } public int hashCode() { return AccountNo.hashCode(); } public boolean equals(Object obj) { if(this==obj) return true; if(obj!=null&&obj.getClass()==Account.class) { Account a=(Account)obj; return a.getAccountNo().equals(AccountNo); } return false; } }

package Test2; public class Test extends Thread { private Account account; private double drawAccount; /** * @param account * @param drawAccount */ public Test(String name,Account account, double drawAccount) { super(name); this.account = account; this.drawAccount = drawAccount; } @Override public void run() { account.draw(drawAccount); } public static void main(String[] args) { Account a=new Account("3242332",1000); Test t1=new Test("甲", a, 600); Test t2=new Test("乙", a, 600); t1.start(); t2.start(); } }

package Test2; public class Test extends Thread { private Account account; private double drawAccount; /** * @param account * @param drawAccount */ public Test(String name,Account account, double drawAccount) { super(name); this.account = account; this.drawAccount = drawAccount; } @Override public void run() { account.draw(drawAccount); } public static void main(String[] args) { Account a=new Account("3242332",1000); Test t1=new Test("甲", a, 600); Test t2=new Test("乙", a, 600); t1.start(); t2.start(); } }
釋放同步監視器的鎖定
同步鎖(Lock)
死鎖