--java多線程和單例模式--java學習日記3(基礎知識)


從進程說起。

進程:是一個正在執行中的程序

每一個進程執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元。

線程:就是進程中的一個獨立的控制單元。

線程控制着進程的執行。

一個進程中至少有一個線程。

Java VM啓動的時候會有一個進程java.exe

該進程中至少有一個線程負責java程序的執行。而且這個線程運行的代碼存在於main方法中,該線程稱之爲主線程。

擴展:其實更細節說明jvm,jvm啓動不止一個線程,還有負責垃圾回收機制的線程。

在某一時刻,只能有一個程序在運行(多核除外),多線程的執行具有隨機性,至於執行多長時間,cpu說的算。

實現多線程的兩個方法:

一、繼承Thread類。步驟:

1.定義類繼承Thread

2.重寫run方法。原因:Thread類用於描述線程,該類就定義了一個功能用於存儲線程要運行的代碼,該存儲功能就是run方法。也就是說,run方法用於存儲線程要運行的代碼。目的:將自定義的代碼存儲在run方法中,讓線程執行。

3.調用start方法(啓動線程,調用run方法)。區別:Thread  t = new Thread();   t.run();只是調用方法,而線程創建了,並沒有運行。定義子類對象的同時線程被創建,調用start纔開啓線程。

 

Thread常用方法:

static Thread currentThread();獲取當前線程對象

getName():獲取線程名稱

設置線程名稱:setName()或構造函數。

局部變量在每個線程中都有獨立的一個存儲空間。

 

    二、實現Runnable。步驟:

1.定義類實現Runnable接口

2.覆蓋Runnable接口中的run方法。將線程要運行的代碼放在該run方法中。

3.通過Thread類建立線程對象new Thread(Runnable r);

4.將Runnable接口的子類對象。爲什麼要將Runnable接口的子類對象作爲實際參數傳遞給Thread的構造函數?因爲,自定義的run方法所屬的對象是Runnable接口的子類對象,所以要讓線程去執行指定對象的run方法,就必須明確該run方法所屬對象。

5.調用Thread類的start方法開啓線程並調用Runnable接口子類的run方法 

 

實現方式和繼承方式有什麼區別呢?

實現方式的好處:避免了java語言單繼承的侷限性在定義線程時,建議使用實現方式。

區別:線程代碼存放位置不同

繼承Thread:線程代碼存放在Thread子類的run方法中。

實現Runnable:線程代碼存在接口的子類的run方法中。

 

多線程出現安全問題的原因:

當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來,導致共享數據的錯誤。

 

解決辦法:對多條操作共享數據的語句,只能讓一個線程都執行完。在執行過程中,其他線程不能參與進來。

Java對於多線程的安全問題提供了專業的解決方式,

就是同步代碼塊

synchronized(對象)

{

需要同步的代碼

}

對象如同鎖,持有鎖的線程可以在同步中執行

沒有持有鎖的線程即使獲取cpu的執行權也進不去,因爲沒有獲取鎖

同步的前提:

1.必須要有兩個或者兩個以上的線程。

2.必須是多個線程使用同一個鎖。

必須保證同步中只能有一個線程在運行

好處:解決了多線程的安全問題

弊端:多個線程需要判斷鎖,較爲消耗資源。

 

如何判斷代碼中是否有安全問題?

1.明確哪些代碼是多線程運行代碼。

2.明確共享數據。

3.明確多線程運行代碼中哪些語句是操作共享數據的。 

同步函數  修飾符 synchronized 返回類型 函數名(參數列表){}

函數需要被對象調用,因此,同步函數持有的鎖是this

如果同步函數被靜態修飾後,使用的是什麼鎖呢?

通過驗證,不是this,因爲靜態方法中也不可以定義this。

靜態進內存時,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象。類名.class  該對象的類型是Class。

靜態的同步方法,使用的鎖是該方法所在類的字節碼文件對象 。

 

延遲加載的單例設計模式的實例(懶漢式)

class Single

{

private static Single s = null;

private Single(){}

public static Single getInstance()

{

     if(s == null)

     {

      sychronized(Single.class)

        {

  		if(s == null)

  		{

                     s = new Single();

  		}

  	}

     }

   return s;

}

} 

 

懶漢式和餓漢式的區別:

懶漢式特點是實例的延遲加載。但多線程訪問下可能會有問題,解決方法:加同步代碼塊或同步函數可以實現,但相對低效,雙重判斷可以解決效率問題。加同步時使用的鎖是該對象所在類的字節碼文件對象。

 

死鎖

什麼是死鎖?簡單來說,死鎖就是A線程持有b鎖、B線程持有a鎖,它們都要等對方釋放持有的鎖才能繼續運行,造成兩個線程進入循環等待,形成死鎖。

產生原因:同步中嵌套同步

解決辦法:一旦死鎖發生,必須強制停止其中一個產生死鎖的線程,打破死循環,讓其他線程繼續執行。

 死鎖代碼示例:

class DeadLockTest implements Runnable // 定義一個類實現Runnable接口
{
/**
	本程序演示了一個死鎖
*/
	private boolean flag;
	public DeadLockTest(boolean flag)
	{
		this.flag = flag;
	}

	public void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(MyLock.locka)
				{
					System.out.println("if locka");
					synchronized(MyLock.lockb)
					{
						System.out.println("if lockb");
					}
				}
			}
		}
		else 
		{
			while(true)
			{
				synchronized(MyLock.lockb)
				{
					System.out.println("else lockb");
					synchronized(MyLock.locka)
					{
						System.out.println("else locka");
					}
				}
			}
		}
	}

	public static void main(String[] args) 
	{
		Thread t1 = new Thread(new DeadLockTest(true));// 用實現Runnable接口方式創建一個線程
		Thread t2 = new Thread(new DeadLockTest(false));
		t1.start();  // 啓動線程t1
		t2.start(); // 啓動線程t2
		System.out.println("Hello World!");
	}
}

class MyLock  // 用來定義線程中用到的鎖
{
	static Object locka = new Object() ;
	static Object lockb = new Object() ;
}

 

線程間通訊 

其實就是多個線程在操作同一個資源,但是操作的動作不同。

等待喚醒機制 

wait(),notify(),notifyAll()  都是用在同步中,因爲要對持有監視器(鎖)的線程操作。

所以要使用在同步中,因爲只有同步才具有鎖。

爲什麼這操作線程的方法要定義在Object中?

因爲這些方法在操作同步中線程時,都必須要標識它們所操作線程持有的鎖

 

只有同一個鎖上的被等待線程,纔可以被同一個鎖上notify喚醒。

不可以對不同鎖中的線程進行喚醒。

 

也就是說,等待和喚醒必須是同一個鎖。

 

而鎖可以是任意對象,所以可以被任意對象調用的方法定義在Object類中。

 

對於多個生產者和消費者。

爲什麼要定義while判斷標記。

原因:讓被喚醒的線程再一次判斷標記。

 

 

爲什麼定義notifyAll,

因爲需要喚醒對方線程。

因爲只用notify,容易出現只喚醒本方線程的情況。導致程序中的所有線程都等待。

 

JDK1.5中提供了多線程升級解決方案

將同步Synchronized替換成顯式Lock操作。

將Object中的wait,notify,notifyAll,替換成了Condition對象。

該對象可以對Lock鎖進行獲取

該示例中,實現了本方只喚醒對方操作。

Lock.unlock();一定要執行,應放在finally語句塊中

 

如何停止線程?

Stop方法已經過時。如何停止線程呢?

只有使run方法結束

開啓多線程運行,運行代碼通常是循環結構。

只要控制住循環,就可以讓run方法結束,也就是線程結束。

特殊情況:當線程處於凍結狀態,就不會讀取到標記,那麼線程就無法結束

 

當沒有指定的方式讓凍結的線程恢復到運行狀態時,這時需要對凍結狀態進行清除。強制讓線程恢復到運行狀態中來,這樣就可以操作標記來讓線程結束。

Thread類提供該方法 interrupt(); sleep,wait,join均可中斷。

 

Thread類中的常用方法

public final void setDaemon(boolean f)設置爲守護線程或用戶線程(後臺線程),若正在運行的線程全爲守護線程時,java虛擬機退出。要在線程啓動即start之前設置

public final void join() throws InterruptedException 等待該線程終止

join:當A線程執行到了B線程.join()方法時,A就會等待。等B線程都執行完,A纔會執行。

setPriority():MIN_PRIORITY==1,NORM_PRIORITY==5,MAX_PRIORITY==10

Thread.yield();主動釋放執行權。暫停當前正在執行的線程對象,執行其他線程。使線程都有執行機會。

發佈了24 篇原創文章 · 獲贊 17 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章