以下內容整理自:http://blog.csdn.net/wtyvhreal/article/details/44176369
線程:是指進程中的一個執行流程。 線程與進程的區別:每個進程都需要操作系統為其分配獨立的內存地址空間,而同一進程中的所有線程在同一塊地址空間中工作,這些線程可以共享同一塊內存和系統資源。
Java實現多線程的方法有兩種,一是繼承Thread類、二是實現Runnable接口。
一、繼承Thread類
public class ThreadTest extends Thread { @Override public void run() { System.out.println("this thread name is:"+Thread.currentThread().getName()); } /** * @param args */ public static void main(String[] args) { ThreadTest t = new ThreadTest(); t.setName("myTestThread"); t.start(); } }
- 繼承Thread后需要覆蓋run()來實現自己的業務邏輯;
- 一個線程的啟動是調用start方法,而不是run方法;
- 當給線程設置名稱的時候,就可以得到設置的線程名稱。
二、實現Runnable接口
public class RunnableTest implements Runnable { @Override public void run() { System.out.println("this thread name is:"+Thread.currentThread().getName()); } public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); RunnableTest r = new RunnableTest(); Thread t = new Thread(r); t.start(); } }
- 實現Runnable接口需要實現run方法,在run方法里面實現自己的業務邏輯;
- 實現Runnable接口的類自己不能啟動線程,需要將此類的對象傳遞給Thread,由Thread的start方法啟動。
- main函數是java運行啟動的入口,它是由一個name叫main線程調用的;如果一個線程沒有專門設置名稱,程序會默認的將名稱設置為Thread-num,num是從0開始累加的數字。
三、線程的狀態轉換
- 當一個線程執行了start方法后,不代表這個線程就會立即被執行,只代表這個線程處於可運行的狀態,最終由OS的線程調度來決定哪個可運行狀態下的線程被執行。
- 一個線程一次被選中執行是有時間限制的,這個時間段叫做CPU的時間片,當時間片用完但線程還沒有結束時,這個線程又會變為可運行狀態,等待OS的再次調度;在運行的線程里執行Thread.yeild()方法同樣可以使當前線程變為可運行狀態。
- 在一個運行中的線程等待用戶輸入、調用Thread.sleep()、調用了其他線程的join()方法,則當前線程變為阻塞狀態。
- 阻塞狀態的線程用戶輸入完畢、sleep時間到、join的線程結束,則當前線程由阻塞狀態變為可運行狀態。
- 運行中的線程調用wait方法,此線程進入等待隊列。
- 運行中的線程遇到synchronized同時沒有拿到對象的鎖標記、等待隊列的線程wait時間到、等待隊列的線程被notify方法喚醒、有其他線程調用notifyAll方法,則線程變成鎖池狀態。
- 鎖池狀態的線程獲得對象鎖標記,則線程變成可運行狀態。
- 運行中的線程run方法執行完畢或main線程結束,則線程運行結束。
四、線程同步各個方法的區別
- Thread.yield()當前運行的線程變成可運行狀態。
- t2.join() 使得當前線程處於阻塞狀態直到t2線程執行完畢。
- Thread.sleep()使得當前線程處於阻塞狀態直到sleep的時間結束。
- wait、notify、notifyAll方法是Object類的方法,其調用環境必須有synchronized的同步塊中調用,否則會拋java.lang.IllegalMonitorStateException異常。
五、線程池
多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。
假設一個服務器完成一項任務所需時間為:T1 創建線程時間,T2 在線程中執行任務的時間,T3 銷毀線程時間。
- 如果:T1 + T3 遠大於 T2,則可以采用線程池,以提高服務器性能。
- 一個線程池包括以下四個基本組成部分:
1、線程池管理器(ThreadPool):用於創建並管理線程池,包括 創建線程池,銷毀線程池,添加新任務;
2、工作線程(PoolWorker):線程池中線程,在沒有任務時處於等待狀態,可以循環的執行任務;
3、任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它規定了任務的入口,任務執行完后的收尾工作,任務的執行狀態等;
4、任務隊列(taskQueue):用於存放沒有處理的任務。提供一種緩沖機制。
線程池技術關注如何縮短或調整T1,T3時間的技術,從而提高服務器程序性能。它把T1,T3分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T3的開銷了。線程池不僅調整T1,T3產生的時間段,而且它還顯著減少了創建線程的數目,看一個例子:
假設一個服務器一天要處理50000個請求,並且每個請求需要一個單獨的線程完成。在線程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目,而如果服務器不利用線程池來處理這些請求則線程總數為50000。一般線程池大小是遠小於50000。所以利用線程池的服務器程序不會為了創建50000而在處理請求時浪費時間,從而提高效率。
線程調度模型:分時調度模型和搶占式調度模型 。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從對象的等待池中隨機選擇一個線程,把它轉到對象的鎖池中。