線程(Thread)和異常


線程Thread

實現多線程有兩種方式:

  1、繼承Thread類(本質也是實現Runnable接口的一個實例)

  Thread類源碼

public class Thread implements Runnable {}

 

定義一個線程

public class MyThreadextends Thread{ public void run(){     //重寫run方法
 } } MyThread thread1 = new MyThread(); thread1.start();

 

  啟動線程唯一的方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啟動一個新線程,並執行run()方法。

  2、實現Runnable接口

  如果一個類已經extends另一個類,就無法直接extends Thread,此時,必須實現一個Runnable接口

public class MyThread extends otherclass implements Runnable{ public void run(){     //重寫run方法
 } }

 

啟動線程,需要首先實例化一個Thread,並傳入自己的Thread實例

MyThread mythread = new MyThread();

Thread thread  = new Thread(mythread);

thread.start();

區別

  繼承Thread類,來實現多線程,相當於拿出三件事,分別交給三個線程來做。因為MyThread繼承Thread,所以在new MyThread的時候在創建三個對象的同時創建了三個線程。

  實現Runnable的,相當於拿出一件事讓三個線程去做, new MyThread 相當於創建一個任務,然后實例化三個Thread。多線程的實例變量也是共享的,故而實現Runnable接口適合於資源共享。

 

解析

(1)start()方法

  用start方法來啟動線程,是真正實現了多線程,通過調用Thread類的start()方法來啟動一個線程,這時此線程處於就緒(可運行)狀態,並沒有運行,一旦得到cpu時間片,就開始執行run()方法(run()方法是開始執行的點)。但要注意的是,此時無需等待run()方法執行完畢,即可繼續執行下面的代碼。所以run()方法並沒有實現多線程。

(2)run()方法

  run()方法只是類的一個普通方法而已,如果直接調用Run方法,程序中依然只有主線程這一個線程,其程序執行路徑還是只有一條,還是要順序執行,還是要等待run方法體執行完畢后才可繼續執行下面的代碼。

(3)線程可以設置優先級大小:thread1.setPriority();

(4)Thread類是在java.lang包中定義的。一個類只要繼承了Thread類同時重寫了本類中的run方法就可以實現多線程操作,但是一個類只能繼承一個父類。

  Runnable接口避免繼承的局限,一個類可以繼承多個接口。適用於資源的共享。

(5)除了Thread、Runnable還有Callable等方式來實現線程。

(6)wait()方法必須進行try catch異常捕捉,線程拋出的錯誤一般與InterruptedException有關。

(7)將一個線程設置成daemon(后台線程),意味着主線程結束,后台線程自動結束。

(8)線程停止有三種方法:①調用stop()方法②線程執行完成③異常拋出。

 

守護線程(Daemon Thread

  守護線程也稱“服務進程”、“后台線程”,是指在程序運行時在后台提供一種通用服務的線程。如果用戶線程已經全部退出運行,只剩下守護線程存在了,JVM也就退出了。

  守護線程一般具有較低的優先級,用戶也可以自己設置守護線程,在調用start()方法啟動線程之前調用對象的setDaemon(true)方法,設置為false,則表示的是用戶進程模式。當一個守護線程中產生了其他線程,這些新的線程還是守護線程。

  典型的例子就是垃圾回收器

 

線程同步

(1)同步方法

         用synchronized關鍵字修飾方法。由於java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,需要獲得內置鎖,否則就處於阻塞狀態。

(2)同步代碼塊

         用synchronized關鍵字修飾的語句塊,語句塊會自動被加上內置鎖,從而實現同步。

         同步是一種高開銷的操作,因此應該盡量減少同步的內容。

(3)wait和notify

         wait():使線程處理等待狀態,並且釋放所持有的對象的lock。

(4)使用特殊變量(volatile)實現線程同步

(5)使用重入鎖實現線程同步

(6)使用局部變量實現線程同步

(7)使用阻塞隊列實現線程同步

 

volatile 與 synchronized

  volatile主要用在多個線程感知實例變量被更改了場合,從而使得各個線程獲得最新的值。它強制線程每次從主內存中講到變量,而不是從線程的私有內存中讀取變量,從而保證了數據的可見性。

比較

  ①volatile輕量級,只能修飾變量。synchronized重量級,還可修飾方法。

  ②volatile只能保證數據的可見性,不能用來同步,因為多個線程並發訪問volatile修飾的變量不會阻塞。

  synchronized不僅保證可見性,而且還保證原子性,因為,只有獲得了鎖的線程才能進入臨界區,從而保證臨界區中的所有語句都全部執行。多個線程爭搶synchronized鎖對象時,會出現阻塞。

線程安全性

  線程安全性包括兩個方面:①可見性;②原子性。

  從上面自增的例子中可以看出:僅僅使用volatile並不能保證線程安全性。而synchronized則可實現線程的安全性。

 

線程之間狀態轉換

  線程從新建到結束一般有5個狀態,為新建、可運行、運行、阻塞、結束,其中阻塞不是必經狀態!

 

1. 新建(new):新創建了一個線程對象。

2. 可運行(runnable):線程對象創建后,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取cpu 的使用權。

3. 運行(running):可運行狀態(runnable)的線程獲得了cpu 時間片(timeslice),執行程序代碼。

4. 阻塞(block):阻塞狀態是指線程因為某種原因放棄了cpu 使用權,也即讓出了cpu timeslice,暫時停止運行。直到線程進入可運行(runnable)狀態,才有機會再次獲得cpu timeslice 轉到運行(running)狀態。阻塞的情況分三種:

  (一). 等待阻塞:運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。

  (二). 同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池(lock pool)中。

  (三). 其他阻塞:運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入可運行(runnable)狀態。

5. 死亡(dead):線程run()、main() 方法執行結束,或者因異常退出了run()方法,則該線程結束生命周期。死亡的線程不可再次復生。

 

sleep()和wait()

  sleep()是線程類(Thread)的方法,導致此線程暫停執行指定時間,給執行機會給其他線程,但是監控狀態依然保持,到時后會自動恢復。調用sleep不會釋放對象鎖。

  wait()是Object類的方法,對此對象調用wait方法導致本線程放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象發出notify方法(或notifyAll)后本線程才進入對象鎖定池准備獲得對象鎖進入就緒狀態

區別:

  sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常。

  sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。

 

  Condition是在java 1.5中才出現的,它用來替代傳統的Object的wait()、notify()實現線程間的協作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()這種方式實現線程間協作更加安全和高效。因此通常來說比較推薦使用Condition,阻塞隊列實際上是使用了Condition來模擬線程間協作。

  Condition是個接口,基本的方法就是await()和signal()方法;

 

死鎖

死鎖產生的原因及必要條件

    產生死鎖的原因主要是:

  (1)因為系統資源不足。

  (2)進程運行推進的順序不合適。

  (3)資源分配不當等。

  如果系統資源充足,進程的資源請求都能夠得到滿足,死鎖出現的可能性就很低,否則就會因爭奪有限的資源而陷入死鎖。其次,進程運行推進順序與速度不同,也可能產生死鎖。

    產生死鎖的四個必要條件:

  (1)互斥條件:一個資源每次只能被一個進程使用。

  (2)請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。

  (3)不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。

  (4)循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。

  這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。

 

進程、線程、纖程

         進程通常被定義為一個正在運行的程序的實例,是具有一定獨立功能的程序關於某個數據集合上的依次運動活動,是系統進行資源分配和調度的一個獨立單位。

         線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬於一個進程的其他線程共享進程所擁有的全部資源。

         纖程是以用戶方式代碼來實現的,內核並不知道纖程,並且他們是根據用戶定義的算法來調度的。由於定義了纖程的調度算法,因此,就內核而言,纖程采用非搶占式調度的方式。

(1)關系

         一個線程可以創建和撤銷另一個線程;同一個進程中的多個線程之間可以並發執行。相對進程而言,線程是一個更加接近於執行體的概念,它可以與同進程中的其他線程共享數據,但擁有自己的棧空間,擁有獨立的執行序列。

(2)區別

  進程和線程的主要差別在於它們是不同的操作系統資源管理方式。

  進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。

  線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變量的並發操作,只能用線程,不能用進程。

    a. 一個程序至少有一個進程,一個進程至少有一個線程。

    b. 線程的划分尺度小於進程,使得多線程程序的並發性高。

    c. 進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。

    d. 線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。

    e. 從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分可以同時執行。但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。

線程安全和線程不安全

  線程安全就是多線程訪問的時候,采用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可以使用,不會出現數據不一致或者數據污染。Vector、HashTable、StringBuffer

  線程不安全就是不提供數據的訪問保護,有可能出現多個線程先后更改數據造成所得到的數據是臟數據。

   線程安全的問題就是由全局變量以及靜態變量引起的,若每個線程中對全局變量,靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時進行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。

  舉例:開啟1000個線程分別向ArrayList中添加元素,每個對象添加100個元素,最終size應該為100000,結果會發現size會小於100000

 

 

異常

 

 

  Java是通過面向對象的方法進行異常處理,把各種不同的異常進行分類,並提供了良好的接口。在Java中,每一個異常都是一個對象,它是Throwable類或者其他子類的實例。

當一個方法出現異常后便拋出一個異常對象,該對象中包含異常信息,調用這個對象的方法可以捕獲到這個異常並進行處理。

 

 

  異常的繼承結構:基類為Throwable,Error和Exception繼承Throwable,RuntimeException和IOException等繼承Exception。

    非RuntimeException一般是外部錯誤(非Error),其必須被 try{}catch語句塊所捕獲

    手動創建一個異常需要寫new exception

  運行異常,可以通過java虛擬機來自行處理。非運行異常,我們應該捕獲或者拋出。

  throw關鍵字用於拋出異常

  throws關鍵字可以在方法上聲明該方法要拋出的異常,然后在方法內部通過throw拋出異常對象。

  運行時異常:都是RuntimeException類及其子類異常,如NullPointerException、IndexOutOfBoundsException等,這些異常是不檢查異常,程序可以選擇捕獲處理,也可以不處理,一般都是由邏輯錯誤引起的。

  非運行時異常:是RuntimeException以外的異常,類型上都屬於Exception類及其子類。必須進行異常處理,不處理,程序不能編譯通過。

 

finally

    兩種情況下finally語句是不會被執行的:

(1)try語句沒有被執行到,如在try語句之前就返回了,這樣finally語句就不會執行,這也說明了finally語句被執行的必要而非充分條件是:相應的try語句一定被執行到。

(2)在try塊中有System.exit(0);這樣的語句,System.exit(0);是終止Java虛擬機JVM的,連JVM都停止了,所有都結束了,當然finally語句也不會被執行到。

  1、finally語句是在try的return語句執行之后,return返回之前執行。

  2、finally塊中的return語句會覆蓋try塊中的return返回。

  3、如果finally語句中沒有return語句覆蓋返回值,那么原來的返回值可能因為finally里的修改而改變也可能不變。

  4、finally語句先於 return 和 throw語句執行。

總結

  finally塊的語句在try或catch中的return語句執行之后返回之前執行且finally里的修改語句可能影響也可能不影響try或catch中 return已經確定的返回值,若finally里也有return語句則覆蓋try或catch中的return語句直接返回。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM