java中線程學習總結


一、對進程、線程概念的理解:

進程:正在運行的應用程序。每個進程都有自己獨立的地址空間(內存空間),當我們點擊桌面的IE瀏覽器時,就啓動了一個進程,操作系統就會爲該進程分配獨立的地址空間。

線程:其實就是進程中一個程序執行控制單元,一條執行路徑。每個線程在堆棧區中都有自己的執行空間,自己的方法區、自己的變量。進程負責的是應用程序的空間的標示。線程負責的是應用程序的執行順序。

多線程應用程序:一個應用程序就是一個進程,當該應用程序中有多個線程運行時,就稱這個程序爲多線程應用程序。


二、創建線程的方式:

方式一:繼承Thread類,由子類複寫run方法。

步驟:

1,定義一個類繼承Thread類;

2,複寫run方法,將要讓線程運行的代碼都存儲到run方法中;

3,通過創建Thread類的子類對象,創建線程對象;

4,調用線程的start方法,開啓線程,並執行run方法。


class MyThread extends Thread
{
	public void run()
	{
		System.out.println(Thread.currentThread().getName()+"運行MyThread.run()方法");
	}
}
class ThreadDemo 
{
	public static void main(String[] args) 
	{
		MyThread myThread1 = new MyThread();
		MyThread myThread2 = new MyThread();
		myThread1.start();		
		myThread2.start();		
	}
}

運行結果:
Thread-0運行MyThread.run()方法
Thread-1運行MyThread.run()方法



方式二:實現一個接口Runnable。

步驟:

1,定義類實現Runnable接口。

2,覆蓋接口中的run方法(用於封裝線程要運行的代碼)。

3,通過Thread類創建線程對象。

4,將實現了Runnable接口的子類對象作爲實際參數傳遞給Thread類中的構造函數。

爲什麼要傳遞呢?因爲要讓線程對象明確要運行的run方法所屬的對象。

5,調用Thread對象的start方法。開啓線程,並運行Runnable接口子類中的run方法。


class MyThread implements Runnable
{
	public void run()
	{
		System.out.println(Thread.currentThread().getName()+"運行MyThread.run()方法");
	}
}
class ThreadDemo2 
{
	public static void main(String[] args) 
	{
		MyThread myThread1 = new MyThread();
		MyThread myThread2 = new MyThread();
		
		Thread t1 = new Thread(myThread1);	
		Thread t2 = new Thread(myThread1);	
		t1.start();
		t2.start();
				
	}
}


三、爲什麼要有Runnable接口的出現?

1通過繼承Thread類的方式,可以完成多線程的建立。但是這種方式有一個侷限性,如果一個類已經有了自己的父類,就不可以繼承Thread類,因爲java單繼承的侷限性。可是該類中的還有部分代碼需要被多個線程同時執行。這時怎麼辦呢?只有對該類進行額外的功能擴展,java就提供了一個接口Runnable。這個接口中定義了run方法,其實run方法的定義就是爲了存儲多線程要運行的代碼。所以,通常創建線程都用第二種方式。因爲實現Runnable接口可以避免單繼承的侷限性。

 2其實是將不同類中需要被多線程執行的代碼進行抽取,將這些代碼單獨定義到接口中,爲其他類進行功能擴展提供了前提。所以Thread類在描述線程時,內部定義的run方法,也來自於Runnable接口。而且,繼承Thread,是可以對Thread類中的方法進行子類複寫的。如果不需要做這個複寫動作只爲定義線程代碼存放位置的話,實現Runnable接口更方便一些。所以Runnable接口將線程要執行的任務封裝成了對象。


四、多線程的安全問題

1、產生安全問題的原因:

        當一個線程在執行多條語句並運算一份數據時,其他線程參與進來,並操作了同一份數據,導致了錯誤數據的產生。

2、安全問題的產生涉及到兩個因素:

   a,多個線程在操作共享數據。

   b,有多條語句對共享數據進行運算。

3、解決安全問題的原理:
       只要將操作共享數據的語句在某一時段讓一個線程執行完,在執行過程中,其他線程不能進來執行就可以解決這個問題。

如何進行多句操作共享數據代碼的封裝呢?

java中提供了一個解決方式:就是同步代碼塊。

格式:

synchronized(對象) //任意對象都可以,這個對象就是鎖。

需要被同步的代碼;

}

同步的好處:解決了線程安全問題。

同步的弊端:相對降低性能,因爲判斷鎖需要消耗資源;會產生死鎖。


 定義同步是有前提的:

1,必須要有兩個或者兩個以上的線程,才需要同步。

2,多個線程必須保證使用的是同一個鎖。

 

同步的第二種表現形式:

同步函數:其實就是將同步關鍵字定義在函數上,讓函數具備了同步性。

 

同步函數是用的哪個鎖呢?

通過驗證,函數都有自己所屬的對象this,所以同步函數所使用的鎖就是this鎖。

 

當同步函數被static修飾時,這時的同步用的是哪個鎖呢?

靜態函數在加載時所屬於類,這時有可能還沒有該類產生的對象,但是該類的字節碼文件加載進內存就已經被封裝成了對象,這個對象就是該類的字節碼文件對象。

所以靜態加載時,只有一個對象存在,那麼靜態同步函數使用的鎖就是這個對象。

這個對象就是 類名.class

 

同步代碼塊和同步函數的區別?

同步代碼塊使用的鎖可以是任意對象。

同步函數使用的鎖是this,靜態同步函數使用的鎖是該類的字節碼文件對象。

 

在一個類中只有一個同步,可以使用同步函數。如果有多同步,必須使用同步代碼塊,來確定不同的鎖。所以同步代碼塊相對靈活一些。

同步死鎖:將同步進行嵌套(例如同步函數中有同步代碼塊,同步代碼塊中還有同步函數)會產生死鎖現象。


Lock接口:多線程在JDK1.5版本升級時,推出一個接口Lock接口

解決線程安全問題使用同步的形式,(同步代碼塊,要麼同步函數)其實最終使用的都是鎖機制。

 

到了後期版本,直接將鎖封裝成了對象。線程進入同步就是具備了鎖,執行完,離開同步,就是釋放了鎖。

在後期對鎖的分析過程中,發現,獲取鎖,或者釋放鎖的動作應該是鎖這個事物更清楚。所以將這些動作定義在了鎖當中,並把鎖定義成對象。

 

所以同步是隱示的鎖操作,而Lock對象是顯示的鎖操作,它的出現就替代了同步。

 

在之前的版本中使用Object類中wait、notify、notifyAll的方式來完成的。那是因爲同步中的鎖是任意對象,所以操作鎖的等待喚醒的方法都定義在Object類中。

 

而現在鎖是指定對象Lock。所以查找等待喚醒機制方式需要通過Lock接口來完成。而Lock接口中並沒有直接操作等待喚醒的方法,而是將這些方式又單獨封裝到了一個對象中。這個對象就是Condition,將Object中的三個方法進行單獨的封裝。並提供了功能一致的方法 await()signal()signalAll()體現新版本對象的好處。

< java.util.concurrent.locks >Condition接口:await()signal()signalAll()



五、線程狀態的轉換



新建狀態(New):新創建了一個線程對象。
就緒狀態(Runnable):線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
阻塞狀態(Blocked):阻塞狀態是線程因爲某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。

死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。

阻塞的情況分三種:
1)等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
2)同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM把該線程放入鎖。
3)其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。


1、Java中關於線程調度的API最主要的有下面幾個:

線程睡眠:Thread.sleep(long millis)方法
線程等待:Object類中的wait()方法
線程讓步:Thread.yield() 方法
線程加入:join()方法
線程喚醒:Object類中的notify()方法
關於這幾個方法的詳細應用,可以參考SUN的API。


2、這幾個方法的區別和使用。


1)sleep方法與wait方法的區別:

sleep方法是靜態方法,wait方法是非靜態方法。
sleep方法在時間到後會自己“醒來”,但wait不能,必須由其它線程通過notify(All)方法讓它“醒來”。sleep方法通常用在不需要等待資源情況下的阻塞,像等待線程、數據庫連接的情況一般用wait。


2)sleep/wait與yeld方法的區別:調用sleep或wait方法後,線程即進入block狀態,而調用yeld方法後,線程進入runnable狀態。


3)wait與join方法的區別:
wait方法體現了線程之間的互斥關係,而join方法體現了線程之間的同步關係。
wait方法必須由其它線程來解鎖,而join方法不需要,只要被等待線程執行完畢,當前線程自動變爲就緒。join方法的一個用途就是讓子線程在完成業務邏輯執行之前,主線程一直等待直到所有子線程執行完畢。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章