天天看點

黑馬程式員--javaSE--多線程基礎總結

------- android教育訓練、java教育訓練、期待與您交流! ----------

在寫這篇部落格之前,我想說的是:java多線程終于弄通順了一遍。雖說以前學過,

但是好久沒用,現在都忘得差不多了,還好借寫部落格的機會又重新複習了下,也體會到了

在學習知識時,做筆記,做總結的重要性。言歸正傳,開始總結多線程吧。

一、關于java線程的一些基本概念

1.程序:是指在系統中正在運作的一個應用程式;

2.線程:線程也稱作輕量級程序。就象程序一樣,線程在程式中是獨立的、并發的執行路徑,

每個線程有它自己的堆棧、自己的程式計數器和自己的局部變量。但是,與分隔的程序相比,程序中的線程之間的隔離程度要小。

它們共享記憶體、檔案句柄和其它每個程序應有的狀态。

二、java中的多線程

1、任何一個java程式都不是單線程的,即使是一個簡單的helloworld的程式也要包括兩個線程,

一個是主線程,另一個就是我們常說的jvm的垃圾回收線程。主線程是main開始執行的,完成main中所有的語句後死亡。

垃圾回收線程清除被廢棄的對象,并回收他們所占用的記憶體。

2、jdk為java實作多線程提供的類有Thread和接口Runnable具體的應用後面會介紹

3、線程的建立和啟動:

線程的建立有兩種方式,第一種是繼承Thread類重寫其run方法,第二種定義實作Runnable接口的類,并将線程要執行的代碼

寫入到run()方法中

代碼實作:

package com.itheima.thread;

public class TraditionalThread {
	public static void main(String[] args) {
		
	//線程的兩種建立方式
	//1.通過繼承Thread類,重寫run()方法
	new Thread(){
		@Override
		public void run() {
			     System.out.println("1"+this.getName());
			}
		};
	}.start();
	//2.通過Thread的構造方法,傳入實作Runnable接口的類的執行個體
	new Thread(new Runnable() {
		
		@Override
		public void run() {
			System.out.println("2"+ Thread.currentThread().getName());
		}
	}).start();
  }
}
           

這兩種方法在底層都是通過調用Thread類的run方法來實作的,是以當建立一個線程時,又繼承了Thread類,又傳入了實作

Runnable接口的類的執行個體對象則,該線程實際上調用的是繼承Thread類時,内部重寫的方法。

4、線程間的同步、鎖、通信

在介紹線程間的通信時,需要先了解線程常用的幾個方法和基本狀态

線程的建立狀态:當利用new關鍵字建立線程對象執行個體後,它僅僅作為一個對象執行個體存在,JVM沒有為其配置設定CPU時間片等運作資源。

線程的就緒狀态:對處于建立狀态的線程調用Thread類的start()方法将線程的狀态轉換為就緒狀态。這時,

                線程已經得到除CPU時間片之外的其他系統資源,隻等JVM的線程排程器按照線程的優先級對該線程進行排程,

進而使該線程擁有能夠獲得CPU時間片的機會。

線程的休眠狀态:線上程運作過程中可以調用Thread類的sleep()方法,并在方法參數中指定線程的休眠時間将線程狀态轉換為休眠狀态。

               這時,該線程在指定的休眠時間内,在不釋放占用資源的情況下停止運作。時間到達後,線程重新進入運作狀态。處于休眠狀态的線程,

               可能遇上 java.lang.InterruptedException異常,進而被迫停止休眠。

挂起狀态:可以通過調用Thread類的suspend()方法将線程的狀态轉換為挂起狀态。這時,線程将釋放占用的所有資源,由JVM排程轉入臨時存儲空間,

          直至應用程式調用Thread類的resume()方法恢複線程運作。

死亡狀态:死亡狀态:當線程體運作結束或者調用線程對象的Thread類的stop()方法後線程将終止運作,由JVM收回線程占用的資源。

幾個重要的方法:

wait()方法:該方法屬于Object的方法,wait方法的最用是是的目前調用wait方法所在部分的線程停止執行,并釋放目前獲得的調用wait所在的代碼塊的鎖,

并在其他線程代用notify或者noyifyAll方法時恢複到競争鎖狀态(一旦獲得鎖就恢複執行)。

調用wait方法需要注意幾點:

        第一點:wait被調用的時候必須在擁有鎖(即synchronized修飾的)的代碼塊中。

        第二點:恢複執行後,從wait的下一條語句開始執行,因而wait方法總是應當在while循環中調用,以免出現恢複執行後繼續執行的條件不滿足卻繼續執行的情況。

        第三點:若wait方法參數中帶時間,則除了notify和notifyAll被調用能激活處于wait狀态(等待狀态)的線程進入鎖競争外,在其他線程中interrupt它或者參數時間到了之後,該線程也将被激活到競争狀态。

        第四點:wait方法被調用的線程必須獲得之前執行到wait時釋放掉的鎖重新獲得才能夠恢複執行。

notify方法和notifyAll方法:

        notify方法通知調用了wait方法,但是尚未激活的一個線程進入線程排程隊列(即進入鎖競争),注意不是立即執行。并且具體是哪一個線程不能保證。另外一點就是被喚醒的這個線程一定是在等待wait所釋放的鎖。

        notifyAll方法則喚醒所有調用了wait方法,尚未激活的程序進入競争隊列。

注意點:以上兩個方法所喚醒的線程是:喚醒在此對象上等待的線程。

線程中的同步和鎖:

 當多個線程通路同一個資料時,非常容易出現線程安全問題。這時候就需要用線程同步

java中每個對象都有一個内置鎖,一個對象隻有一個鎖。是以,如果一個線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個線程釋放(或傳回)鎖。

這也意味着任何其他線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。

釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。

關于鎖和同步,有一下幾個要點:

1)隻能同步方法,而不能同步變量和類;

2)每個對象隻有一個鎖;當提到同步時,應該清楚在什麼上同步?也就是說,在哪個對象上同步?

3)不必同步類中所有的方法,類可以同時擁有同步和非同步方法。

4)如果兩個線程要執行一個類中的synchronized方法,并且兩個線程使用相同的執行個體來調用方法,那麼一次隻能有一個線程能夠執行方法,另一個需要等待,直到鎖被釋放。也就是說:如果一個線程在對象上獲得一個鎖,就沒有任何其他線程可以進入(該對象的)類中的任何一個同步方法。

5)如果線程擁有同步和非同步方法,則非同步方法可以被多個線程自由通路而不受鎖的限制。

6)線程睡眠時,它所持的任何鎖都不會釋放。

7)線程可以獲得多個鎖。比如,在一個對象的同步方法裡面調用另外一個對象的同步方法,則擷取了兩個對象的同步鎖。

8)同步損害并發性,應該盡可能縮小同步範圍。同步不但可以同步整個方法,還可以同步方法中一部分代碼塊。

9)在使用同步代碼塊時候,應該指定在哪個對象上同步,也就是說要擷取哪個對象的鎖。

代碼展現:(線程間通信的具體應用,這裡不采用消費者與生産者的問題,而是張老師(張孝祥)所列舉的一個面試題

題目要求:子線程循環10次,接着主線程循環100,接着又回到子線程循環10次,接着再回到主線程又循環100,如此循環50次,請寫出程式。

package com.itheima.thread;

public class TraditionalCommu {
	public static void main(String[] args) {
		final Business business = new Business();
		//子線程
		new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=1;i<=50;i++){
					business.sub(i);
				}
			}
		}).start();
		
		//主線程
		for(int i=1;i<=50;i++){
				business.main(i);
			}
		}
		
}

class Business{
	//定義一個boolean類型的标志值,用來作為主線程與子線程之間的切換
	boolean flag = true;
	//子線程要調用的方法
	public synchronized void sub(int i){
		//檢驗标志值是否為true,如果是則執行子線程
		while(flag){
			for(int j=1;j<=10;j++){
				System.out.println("sub thread sequence of " + j + ",loop of " + i);
			}
			//子線程代碼執行完時,将标志值改為false,并喚醒同一把鎖上的線程
			flag = false;
			this.notify();
		}
		//若程式執行到這,則說明,标志值為false,不能執行子線程,等待
		try {
			this.wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}
	
	//主線程要調用的方法
	public synchronized void main(int i){
		//檢驗标志值是否為false,如果是則執行主線程
		while(!flag){
			for(int j=1;j<=100;j++){
				System.out.println("main thread sequence of " + j + ",loop of " + i);
		}
		//主線程代碼執行完時,将标志值改為true,并喚醒同一把鎖上的線程
			flag = true;
			this.notify();
		}
		//若程式執行到這,則說明,标志值為true,不能執行主線程,等待
		try {
			this.wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}
           

三、線程的補充

1.設定線程的優先級:

 Java将線程的優先級分為10個等級,分别用1~10之間的數字表示。數字越大表明線程的優先級别越高。相應地,

 在Thread類中定義了表示線程最低、最高和普通優先級的成員變量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY,

 代表的優先級等級分别為1、10 和5。當一個線程對象被建立時,其預設的線程優先級是5。在建立線程對象之後可以

 調用線程對象的setPriority()方法改變該線程的運作優先級,同樣可以調用getPriority()方法擷取目前線程的優先級。

 2.守護線程;

在Java中有一類被稱為守護(Daemon)線程的,比較特殊的線程,這是一種優先級比較低的線程。守護線程具有最低的優先級,

一般用于為系統中的其他對象和線程提供服務。将一個線程設定為守護線程的方式是線上程對象建立之前調用線程對象的setDaemon()方法。

典型的守護線程例子是Java虛拟機中的垃圾自動回收線程,它始終在低級别的狀态中運作,用于實時監控和管理系統中的可回收資源。

可以通過調用線程對象的isDaemon()方法來判斷某個線程是否是守護線程。

守護線程還有另一層含義:當建立守護線程的父線程終止時,作為子線程的守護線程也自動終止。

反之,如果一個子線程不是守護線程,即使父線程終止了,它也不會終止。

當一個線程被建立時,它預設不是守護線程。

面的代碼片段示範守護線程的用法。線程parent建立了一個子線程child,子線程child的循環體是一個無限循環,

不斷地列印“Child runs”字樣。在child線程不是守護線程的情況下,即使parent線程終止了,child線程仍然在運作:

Thread parent=new Thread()
{
       public void run()
       {
             System.out.println("Parent starts");
             Thread child=new Thread()
             {
                   public void run()
                   {
                         System.out.println("Child starts");
                         while(true)
                         {
                               System.out.println("Child runs");
                         }
                   }
             };
             child.setDaemon(false);
             child.start();
             System.out.println("Parent ends");
       }
};
parent.start();
           

如果希望父線程終止時,子線程自動終止,隻需要将“child.setDaemon(false);”改為“child.setDaemon(true);”即可。

總結:線程是比較繁瑣的和注重細節的,是以一定要打好線程基礎知識。

ps:終于寫完了對于線程的簡單總結,但還有複雜的多個線程通路共享對象和資料需要總結,不過趁熱打鐵,

先去買點零食回來接着寫。