從零開始java多線程到分佈式鎖(二):多線程的之間的線程協作

一:java多線程的JVM運行模型

  首先當一個單線程轉變成一個多線程運作時,我們需要對JVM的內存模型有一些基本的瞭解。首先我們來看一下在多線程的情況下JVM如何運行線程方式,如圖所示:

                      

  網上很多對這個運行模型講解的不是很透徹,這裏我們做一個簡單說明,主要分爲以下幾步:

(1)每一個線程複製出一個內存副本(圖中以共享變量爲主),也就是緩存出一個應用層次的副本,每一個線程都是在本線程             的副本中進行操作,避免每一個線程都要到內存中操作,佔用內存空間(這是多線程的優勢,也是產生多線程的問題的高             發區)。

(2)每一個線程完成或者在一個時間點同步副本至主內存中。

 

二:多線程的線程協作

  從上面的介紹可以看出每一個線程多事由自己獨立的運存副本,在沒有同步之前線程與線程之間是獨立的。那麼我們如何在多個線程運行時去調度不同線程的狀態(注:這裏的調度區別於在A線程中控制B線程的運行狀態,這裏涉及到java的NIO編程模式-Netty)。java提供了2種方式:一是Object的wait()等方法和基於線程的sleep()方法等、

  首先在談到這些方法之前提到一個很多面試會遇到的問題:爲什麼wait()是Object的方法,而不是線程的方法?希望帶着這個問題去往下閱讀。

(1)Object的wait方法

    這個方法是指當前線程進入阻賽狀態即線程掛起,先上一段代碼看一下吧。

package test;

/**
 * 多線程的Object的wait方法
 * @author monxz
 *
 */
public class WaitMethod {
	
	public void  waitTest(){
		System.out.println("======wait開始==========");
		 try {
			            wait(1000);
			  } catch (InterruptedException e) {
			       e.printStackTrace();
		   }
		 System.out.println("======wait結束==========");
	}
	
	public static void main(String[] args) {
		WaitMethod  WaitMethod =new WaitMethod();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				
				WaitMethod.waitTest();
				
			}
		}).start();
	}
}

  然而,這段運行結果盡然拋出異常,如下圖所示:百度了一下,該異常的主要就是說沒有獲取到對象的Monitor鎖造成的,java對於這種異常就是在方法添加synchronized關鍵字來改善,這裏代碼就是不上傳(在waitTest()上添加該關鍵字)

             

(2)Object的notify/notifyAll()方法

  該方法主要是針對(1)中的線程進入阻賽狀態下的線程花旗,前者喚醒單個線程,或者喚醒全部休眠線程。這個方法主要就是針也是基於Object對象的Monitor所實現的。具體代碼就不用上傳了。

(3)Thread的sleep()方法

  sleep方法主要是讓方法進入一個線程暫停,這裏上一個實例代碼:

package test;

/**
 * 多線程的Thread的sleep方法
 * @author monxz
 *
 */
public class sleepMethod {
	
	public   void  sleepTest(){
		System.out.println("======wait開始==========");
		
		System.out.println("======wait結束==========");
	}
	
	public static void main(String[] args) {
		sleepMethod  WaitMethod =new sleepMethod();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				
				WaitMethod.sleepTest();
				
			}
		}).run();
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			
			e.printStackTrace();
		}
		
		
		System.out.println("=======over===========");
	}
}

  其實,很明顯可以看出來,sleep()是在一個線程開始或者結束進行調用方法的這裏明顯就區別於wait方法了(即調用wait方法必須依賴於Object對象及其方法,未定義一個對象是無法使用wait()方法的。而sleep方法只依賴於Thread,即使沒有對象只要使用Thread去調用即可),下面該出一個說明代碼:

package test;

/**
 * 多線程的Thread的sleep方法
 * @author monxz
 *
 */
public class sleepMethod2 {
	
	public   void  sleepTest(){
		System.out.println("======wait開始==========");
		
		System.out.println("======wait結束==========");
	}
	
	public static void main(String[] args) {
		System.out.println("=======begin===========");
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			
			e.printStackTrace();
		}
		
		
		
		
		
		System.out.println("=======over===========");
	}
}

(3)Thread的yield()方法

  這個方法是指讓多個線程進行交替運行,先上一個代碼吧:

package test;

/**
 * 多線程的Thread的sleep方法
 * @author monxz
 *
 */
public class yieldMethod implements Runnable{
	
	@Override
	public void run() {
		for(int i = 0 ;i<5 ; i++ ){
			System.out.println(Thread.currentThread().getName() +"線程運行結果:"+ i );
			Thread.yield();
		}
		
	}
	
	public static void main(String[] args) {
		yieldMethod  yieldMethod =new yieldMethod();
		Thread  t1 = new Thread(yieldMethod, "線程一");
		Thread  t2 =new Thread(yieldMethod,"線程二");
		t1.start();;
		t2.start();;
	}

	
}

    這段代碼理想運行結果是應該如下圖所示,但是很尷尬的是還會出現以下的狀態,後來百度了以下原因是,雖然該方法允許多個線程交替運行,但是這個交替過程不可控,也就是出現以下救過都是正確的,所以一般這個方法很少用,如果要用一定要控制好外部條件:

  

(4)Thread的Join方法 

  Join()方法就是講多線程的方法轉成同步方法,從一開始的JVM圖中可以看出一般情況下,多線程的運行是互不影響的,那麼如果需要將互不影響改爲同步,也就是說A線程一定是在B線程結束才運行的,那麼A線程運行結果同步到主內存,然後B線程複製副本再運行等等,那麼就必須依賴於同步鎖關鍵字,這裏查看join的源碼發現join方法實際上是用wait()來實現的。join方法有一個學名,父線程等待子線程運行結束在運行。

 

三:Object方法和Thread方法的區別於總結

(1)Object方法需要依賴於對象的Monitor鎖實現,所以必須要先定義一個對象。而Thread方法可以直接使用線程Thread來調用方法,這以很好的回答了在本文中間提出的問題了。

(2)Object方法的wait方法是會釋放對象的Monitor對象鎖的,而Thread並不會。

 

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