Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 中級開發 >> Android JAVA多線程,面試者的最愛!

Android JAVA多線程,面試者的最愛!

編輯:中級開發

多線程 
線程:是指進程中的一個執行流程。 
線程與進程的區別:每個進程都需要操作系統為其分配獨立的內存地址空間,而同一進程中的所有線程在同一塊地址空間中工作,這些線程可以共享同一塊內存和系統資源。


如何創建一個線程?

創建線程有兩種方式,如下: 
1、 擴展Java.lang.Thread類 
2、 實現Runnable接口 
Thread類代表線程類,它的兩個最主要的方法是: 
run()——包含線程運行時所執行的代碼 
Start()——用於啟動線程

一個線程只能被啟動一次。第二次啟動時將會拋出Java.lang.IllegalThreadExcetpion異常

線程間狀態的轉換(如圖示)

新建狀態:用new語句創建的線程對象處於新建狀態,此時它和其它的Java對象一樣,僅僅在堆中被分配了內存 
就緒狀態:當一個線程創建了以後,其他的線程調用了它的start()方法,該線程就進入了就緒狀態。處於這個狀態的線程位於可運行池中,等待獲得CPU的使用權 
運行狀態:處於這個狀態的線程占用CPU,執行程序的代碼 
阻塞狀態:當線程處於阻塞狀態時,Java虛擬機不會給線程分配CPU,直到線程重新進入就緒狀態,它才有機會轉到運行狀態。 
阻塞狀態分為三種情況: 
1、 位於對象等待池中的阻塞狀態:當線程運行時,如果執行了某個對象的wait()方法,Java虛擬機就回把線程放到這個對象的等待池中 
2、 位於對象鎖中的阻塞狀態,當線程處於運行狀態時,試圖獲得某個對象的同步鎖時,如果該對象的同步鎖已經被其他的線程占用,JVM就會把這個線程放到這個對象的瑣池中。 
3、 其它的阻塞狀態:當前線程執行了sleep()方法,或者調用了其它線程的join()方法,或者發出了I/O請求時,就會進入這個狀態中。

死亡狀態:當線程退出了run()方法,就進入了死亡狀態,該線程結束了生命周期。 
           或者正常退出 
           或者遇到異常退出 
           Thread類的isAlive()方法判斷一個線程是否活著,當線程處於死亡狀態或者新建狀態時,該方法返回false,在其余的狀態下,該方法返回true.

線程調度 
線程調度模型:分時調度模型和搶占式調度模型 
JVM采用搶占式調度模型。 
所謂的多線程的並發運行,其實是指宏觀上看,各個線程輪流獲得CPU的使用權,分別執行各自的任務。 
(線程的調度不是跨平台,它不僅取決於Java虛擬機,它還依賴於操作系統)

如果希望明確地讓一個線程給另外一個線程運行的機會,可以采取以下的辦法之一 
1、 調整各個線程的優先級 
2、 讓處於運行狀態的線程調用Thread.sleep()方法 
3、 讓處於運行狀態的線程調用Thread.yIEld()方法 
4、 讓處於運行狀態的線程調用另一個線程的join()方法

調整各個線程的優先級 
Thread類的setPriority(int)和getPriority()方法分別用來設置優先級和讀取優先級。 
如果希望程序能夠移值到各個操作系統中,應該確保在設置線程的優先級時,只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY這3個優先級。

線程睡眠:當線程在運行中執行了sleep()方法時,它就會放棄CPU,轉到阻塞狀態。 
線程讓步:當線程在運行中執行了Thread類的yield()靜態方法時,如果此時具有相同優先級的其它線程處於就緒狀態,那麼yield()方法將把當前運行的線程放到運行池中並使另一個線程運行。如果沒有相同優先級的可運行線程,則yIEld()方法什麼也不做。 
Sleep()方法和yIEld()方法都是Thread類的靜態方法,都會使當前處於運行狀態的線程放棄CPU,把運行機會讓給別的線程,兩者的區別在於: 
         1、sleep()方法會給其他線程運行的機會,而不考慮其他線程的優先級,因此會給較低線程一個運行的機會;yIEld()方法只會給相同優先級或者更高優先級的線程一個運行的機會。 
2、當線程執行了sleep(long millis)方法後,將轉到阻塞狀態,參數millis指定睡眠時間;當線程執行了yIEld()方法後,將轉到就緒狀態。 
          3、sleep()方法聲明拋出InterruptedException異常,而yIEld()方法沒有聲明拋出任何異常 
          4、sleep()方法比yIEld()方法具有更好的移植性

等待其它線程的結束:join() 
          當前運行的線程可以調用另一個線程的 join()方法,當前運行的線程將轉到阻塞狀態,直到另一個線程運行結束,它才恢復運行。

定時器Timer:在JDK的Java.util包中提供了一個實用類Timer, 它能夠定時執行特定的任務。

線程的同步 
原子操作:根據Java規范,對於基本類型的賦值或者返回值操作,是原子操作。但這裡的基本數據類型不包括long和double, 因為JVM看到的基本存儲單位是32位,而long 和double都要用64位來表示。所以無法在一個時鐘周期內完成。

自增操作(++)不是原子操作,因為它涉及到一次讀和一次寫。

原子操作:由一組相關的操作完成,這些操作可能會操縱與其它的線程共享的資源,為了保證得到正確的運算結果,一個線程在執行原子操作其間,應該采取其他的措施使得其他的線程不能操縱共享資源。

同步代碼塊:為了保證每個線程能夠正常執行原子操作,Java引入了同步機制,具體的做法是在代表原子操作的程序代碼前加上synchronized標記,這樣的代碼被稱為同步代碼塊。

同步鎖:每個Java對象都有且只有一個同步鎖,在任何時刻,最多只允許一個線程擁有這把鎖。

當一個線程試圖訪問帶有synchronized(this)標記的代碼塊時,必須獲得 this關鍵字引用的對象的鎖,在以下的兩種情況下,本線程有著不同的命運。 
1、 假如這個鎖已經被其它的線程占用,JVM就會把這個線程放到本對象的鎖池中。本線程進入阻塞狀態。鎖池中可能有很多的線程,等到其他的線程釋放了鎖,JVM就會從鎖池中隨機取出一個線程,使這個線程擁有鎖,並且轉到就緒狀態。 
2、 假如這個鎖沒有被其他線程占用,本線程會獲得這把鎖,開始執行同步代碼塊。 
(一般情況下在執行同步代碼塊時不會釋放同步鎖,但也有特殊情況會釋放對象鎖 
如在執行同步代碼塊時,遇到異常而導致線程終止,鎖會被釋放;在執行代碼塊時,執行了鎖所屬對象的wait()方法,這個線程會釋放對象鎖,進入對象的等待池中)

線程同步的特征: 
1、如果一個同步代碼塊和非同步代碼塊同時操作共享資源,仍然會造成對共享資源的競爭。因為當一個線程執行一個對象的同步代碼塊時,其他的線程仍然可以執行對象的非同步代碼塊。(所謂的線程之間保持同步,是指不同的線程在執行同一個對象的同步代碼塊時,因為要獲得對象的同步鎖而互相牽制) 
2、 每個對象都有唯一的同步鎖 
3、 在靜態方法前面可以使用synchronized修飾符。 
4、 當一個線程開始執行同步代碼塊時,並不意味著必須以不間斷的方式運行,進入同步代碼塊的線程可以執行Thread.sleep()或者執行Thread.yIEld()方法,此時它並不釋放對象鎖,只是把運行的機會讓給其他的線程。 
5、 Synchronized聲明不會被繼承,如果一個用synchronized修飾的方法被子類覆蓋,那麼子類中這個方法不在保持同步,除非用synchronized修飾。

線程安全的類: 
1、 這個類的對象可以同時被多個線程安全的訪問。 
2、 每個線程都能正常的執行原子操作,得到正確的結果。 
3、 在每個線程的原子操作都完成後,對象處於邏輯上合理的狀態。

釋放對象的鎖: 
1、 執行完同步代碼塊就會釋放對象的鎖 
2、 在執行同步代碼塊的過程中,遇到異常而導致線程終止,鎖也會被釋放 
3、 在執行同步代碼塊的過程中,執行了鎖所屬對象的wait()方法,這個線程會釋放對象鎖,進入對象的等待池。

死鎖 
當一個線程等待由另一個線程持有的鎖,而後者正在等待已被第一個線程持有的鎖時,就會發生死鎖。JVM不監測也不試圖避免這種情況,因此保證不發生死鎖就成了程序員的責任。

如何避免死鎖 
一個通用的經驗法則是:當幾個線程都要訪問共享資源A、B、C 時,保證每個線程都按照同樣的順序去訪問他們。

線程通信 
Java.lang.Object類中提供了兩個用於線程通信的方法 
1、 wait():執行了該方法的線程釋放對象的鎖,JVM會把該線程放到對象的等待池中。該線程等待其它線程喚醒 
2、 notify():執行該方法的線程喚醒在對象的等待池中等待的一個線程,JVM從對象的等待池中隨機選擇一個線程,把它轉到對象的鎖池中。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved