1. 程式人生 > >Java多執行緒系列——執行緒間通訊

Java多執行緒系列——執行緒間通訊

 

Java多線系列文章是Java多執行緒的詳解介紹,對多執行緒還不熟悉的同學可以先去看一下我的這篇部落格Java基礎系列3:多執行緒超詳細總結,這篇部落格從巨集觀層面介紹了多執行緒的整體概況,接下來的幾篇文章是對多執行緒的深入剖析。

 

執行緒是作業系統中獨立的個體,但這些個體如果不經過特殊的處理就不能成為一個整體。執行緒間的通訊就是成為整體的必用方案之一,可以說,使執行緒間進行通訊後,系統之間的互動性會更強大,在大大提高CPU利用率的同時還會使程式設計師對各執行緒任務在處理的過程中進行有效的把控與監督。

 

執行緒執行狀態

 

 

 

1)新建立一個新的執行緒物件後,再呼叫它的start()方法,系統會為此執行緒分配CPU資源,使其處於Runnable(可執行)狀態,這是一個準備執行的階段。如果執行緒搶佔到CPU資源,此執行緒就處於Running(執行)狀態。  

2)Runnable狀態和Running狀態可相互切換,因為有可能執行緒執行一段時間後,有其他高優先順序的執行緒搶佔了CPU資源,這時此執行緒就從Running狀態變成Runnable狀態。執行緒進入Runnable狀態大體分為如下5種情況:

  1. 呼叫sleep()方法後經過的時間超過了指定的休眠時間。
  2. 執行緒呼叫的阻塞I0已經返回,阻塞方法執行完畢。
  3. 執行緒成功地獲得了試圖同步的監視器。
  4. 執行緒正在等待某個通知,其他執行緒發出了通知。
  5. 處於掛起狀態的執行緒呼叫了resume恢復方法。

3)Blocked是阻塞的意思,例如遇到了一個IO操作,此時CPU處於空閒狀態,可能會轉而把CPU時間片分配給其他執行緒,這時也可以稱為“暫停”狀態。Blocked狀態結束後,進入Runnable狀態,等待系統重新分配資源。出現阻塞的情況有以下幾種:

  1. 執行緒呼叫sleep()方法,主動放棄佔用的處理器資源。
  2. 執行緒呼叫了阻塞式IO方法,在該方法返回前,該執行緒被阻塞。
  3. 執行緒試圖獲得一個同步監視器,但該同步監視器正被其他執行緒所持有。
  4. 執行緒等待某個通知。
  5. 程式呼叫了suspend方法將該執行緒掛起。此方法容易導致死鎖,儘量避免使用該方法。

4)run()方法執行結束後進入銷燬階段,整個執行緒執行完畢。

每個鎖物件都有兩個佇列,一個是就緒佇列,一個是阻塞佇列。就緒佇列儲存了將要獲得鎖的執行緒,阻塞佇列儲存了被阻塞的執行緒。一個執行緒被喚醒後,才會進入就緒佇列,等待CPU的排程;反之,一個執行緒被wait後,就會進入阻塞佇列,等待下一次被喚醒。

 

 

等待與通知機制

一、不使用等待通知機制實現執行緒間通訊:

我們先不使用等待通知機制來看下如何實現執行緒間的通訊:

import java.util.ArrayList;
import java.util.List;

class MyList{
	private List list=new ArrayList();
	public void add() {
		list.add("小馬");
	}
	
	public int size() {
		return list.size();
	}
}

class ThreadA extends Thread{
	private MyList list;
	
	public ThreadA(MyList list) {
		this.list=list;
	}
	
	@Override
	public void run() {
		try {
			for(int i=0;i<10;i++) {
				list.add();
				System.out.println("添加了"+(i+1)+"個元素");
				Thread.sleep(1000);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

class ThreadB extends Thread{
	private MyList list;
	
	public ThreadB(MyList list) {
		this.list=list;
	}
	
	@Override
	public void run() {
		while(true) {
			if(list.size()==5) {
				System.out.println("==5了,執行緒b要退出了");
			}
		}
			
	}
}



public class Test {
	
	
	public static void main(String[] args) {
		MyList list=new MyList();
		ThreadA a=new ThreadA(list);
		a.setName("A");
		a.start();
		ThreadB b=new ThreadB(list);
		b.setName("B");
		b.start();
		
	}
	
}

  

執行結果:

添加了1個元素
添加了2個元素
添加了3個元素
添加了4個元素
添加了5個元素
==5了,執行緒b要退出了
添加了6個元素
添加了7個元素
添加了8個元素
添加了9個元素
添加了10個元素

  

上述程式碼要實現的是當list的size為5時,B執行緒進行操作,實現了AB兩個執行緒之間的通訊,但是有一個弊端,就是執行緒B不停的通過while語句輪詢機制來檢測某一個條件,造成了CPU資源的浪費。

如果輪詢的時間間隔很小,更浪費CPU資源;如果輪詢的時間間隔很大,有可能會取不到想要得到的資料。所以就需要有一種機制來實現減少CPU的資源浪費,而且還可以實現在多個執行緒間通訊,它就是“wait/notify”機制。

 

二、wait/notify機制:

1、什麼是等待通知機制:

等待/通知機制在生活中比比皆是,比如在就餐時就會實現

 

 

 

1)廚師做完一道菜的時間不確定,所以廚師將菜品放到“菜品傳遞臺”上的時間也不確定。

2)服務員取到菜的時間取決於廚師,所以服務員就有“等待”(wait)的狀態。

3)服務員如何能取到菜呢?這又得取決於廚師,廚師將菜放在“菜品傳遞臺”上,其實就相當於一種通知(notify),這時服務員才可以拿到菜並交給就餐者。

4)在這個過程中出現了“等待/通知”機制。

需要說明的是,前面示例中多個執行緒之間也可以實現通訊,原因就是多個執行緒共同訪問同一個變數,但那種通訊機制不是“等待/通知”,兩個執行緒完全是主動式地讀取一個共享變數,在花費讀取時間的基礎上,讀到的值是不是想要的,並不能完全確定。所以現在迫切需要一種“等待/通知”機制來滿足上面的需求。

 

2、等待通知機制的實現:

wait()方法:

方法wait()的作用是使當前執行程式碼的執行緒進行等待,wait()方法是Object類的方法,該方法用來將當前執行緒置入“預執行佇列”中,並且在wait所在的程式碼行處停止執行,直到接到通知或被中斷為止。在呼叫wait()之前,執行緒必須獲得該物件的物件級別鎖,即只能在同步方法或同步塊中呼叫wait()方法。在執行wait()方法後,當前執行緒釋放鎖。在從wait()返回前,執行緒與其他執行緒競爭重新獲得鎖。如果呼叫wait()時沒有持有適當的鎖,則丟擲IlegalMonitorStateException,它是RuntimeException的一個子類,因此,不需要try-catch 語句進行捕捉異常。

 

notify()方法:

方法notify()也要在同步方法或同步塊中呼叫,即在呼叫前,執行緒也必須獲得該物件的物件級別鎖。如果呼叫notify()時沒有持有適當的鎖,也會丟擲llegalMonitorStateException。該方法用來通知那些可能等待該物件的物件鎖的其他執行緒,如果有多個執行緒等待,則由執行緒規劃器隨機挑選出其中一個呈wait狀態的執行緒,對其發出通知notify,並使它等待獲取該物件的物件鎖。需要說明的是,在執行notify()方法後,當前執行緒不會馬上釋放該物件鎖,呈wait狀態的執行緒也並不能馬上獲取該物件鎖,要等到執行notify()方法的執行緒將程式執行完,也就是退出synchronized程式碼塊後,當前執行緒才會釋放鎖,而呈wait狀態所在的執行緒才可以獲取該物件鎖。當第一個獲得了該物件鎖的wait執行緒執行完畢以後,它會釋放掉該物件鎖,此時如果該物件沒有再次使用notify語句,則即便該物件已經空閒,其他wait狀態等待的執行緒由於沒有得到該物件的通知,還會繼續阻塞在wait狀態,直到這個物件發出一個notify 或notifyAll。

 

總結:wait使執行緒停止執行,而notify使停止的執行緒繼續執行

 

我們現在再來用等待通知機制來實現上面的案例,程式碼如下:

 

import java.util.ArrayList;
import java.util.List;

class MyList{
	private static List list=new ArrayList();
	public static void add() {
		list.add("小馬");
	}
	
	public static int size() {
		return list.size();
	}
}

class ThreadA extends Thread{
	
	private Object lock;
	
	public ThreadA(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		try {
			synchronized(lock) {
				if(MyList.size()!=5) {
					System.out.println("wait begin "+System.currentTimeMillis());
					lock.wait();
					System.out.println("wait end   "+System.currentTimeMillis());
				}
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

class ThreadB extends Thread{
private Object lock;
	
	public ThreadB(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		try {
			synchronized(lock) {
				for(int i=0;i<10;i++) {
					MyList.add();
					if(MyList.size()==5) {
						lock.notify();
						System.out.println("已發出通知");
					}
					System.out.println("添加了"+(i+1)+"個元素");
					Thread.sleep(1000);
				}
			}	
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}



public class Test {
	
	
	public static void main(String[] args) throws InterruptedException {
		Object lock=new Object();
		ThreadA a=new ThreadA(lock);
		a.start();
		Thread.sleep(1000);
		ThreadB b=new ThreadB(lock);
		b.start();
		
	}
	

}

 

  

執行結果:

wait begin 1575266897805
添加了1個元素
添加了2個元素
添加了3個元素
添加了4個元素
已發出通知
添加了5個元素
添加了6個元素
添加了7個元素
添加了8個元素
添加了9個元素
添加了10個元素
wait end   1575266908861

  

從結果中我們可以看出,程式一啟動時就輸出了wait begin,但是並沒有立即輸出wait end,這是因為呼叫了wait()方法,使當前執行緒處於等待狀態,暫停執行,當list的size等於5時,呼叫了notify()方法,釋放了等待的執行緒,wait end 便得以輸出讀者可能會有疑惑,為什麼已經執行了notify方法,但是wait end並沒有立即輸出,而是在結尾才輸出,這是因為notify必須在執行完同步synchronized程式碼塊後才釋放鎖。

關鍵字synchronized可以將任何一個Object物件作為同步物件來看待,而Java為每個Object 都實現了wait)和notify0方法,它們必須用在被synchronized同步的Object的臨界區內。通過呼叫wait)方法可以使處於臨界區內的執行緒進入等待狀態,同時釋放被同步物件的鎖。而notify操作可以喚醒一個因呼叫了wait操作而處於阻塞狀態中的執行緒,使其進入就緒狀態。被重新換醒的執行緒會試圖重新獲得臨界區的控制權,也就是鎖,並繼續執行臨界區內wait之後的程式碼。如果發出notify操作時沒有處於阻塞狀態中的執行緒,那麼該命令會被忽略。

wait()方法可以使呼叫該方法的執行緒釋放共享資源的鎖,然後從執行狀態退出,進入等待佇列,直到被再次喚醒。
notify()方法可以隨機喚醒等待佇列中等待同一共享資源的“一個”執行緒,並使該執行緒退出等待佇列,進入可執行狀態,也就是notify()方法僅通知“一個”執行緒。
notifyAll()方法可以使所有正在等待佇列中等待同一共享資源的“全部”執行緒從等待狀態退出,進入可執行狀態。此時,優先順序最高的那個執行緒最先執行,但也有可能是隨機執行,因為這取決於JVM虛擬機器的實現。

 

3、notifyAll()的使用:

notify()方法每次只可以喚醒一個執行緒,notifyAll()方法則可以喚醒所有執行緒

//等待執行緒
class Service{
	public void testMethod(Object lock) {
		try {
			synchronized(lock) {
				System.out.println("begin wait() ThreadName="+Thread.currentThread().getName());
				lock.wait();
				System.out.println("end wait() ThreadName="+Thread.currentThread().getName());
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

//喚醒執行緒
class NotifyThread extends Thread{
	private Object lock;
	
	public NotifyThread(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		synchronized(lock) {
			lock.notify();
		}
	}
}

class ThreadA extends Thread{
	private Object lock;
	public ThreadA(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		Service service=new Service();
		service.testMethod(lock);
	}
}

class ThreadB extends Thread{
	private Object lock;
	public ThreadB(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		Service service=new Service();
		service.testMethod(lock);
	}
}

class ThreadC extends Thread{
	private Object lock;
	public ThreadC(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		Service service=new Service();
		service.testMethod(lock);
	}
}




public class Test {
	
	
	public static void main(String[] args) throws InterruptedException {
		Object lock=new Object();
		ThreadA a=new ThreadA(lock);
		a.start();
		ThreadB b=new ThreadB(lock);
		b.start();
		ThreadC c=new ThreadC(lock);
		c.start();
		Thread.sleep(1000);
		NotifyThread notifyThread=new NotifyThread(lock);
		notifyThread.start();
		
	}
	

}

  

執行結果:

begin wait() ThreadName=Thread-0
begin wait() ThreadName=Thread-1
begin wait() ThreadName=Thread-2
end wait() ThreadName=Thread-0

  

在喚醒執行緒中我們使用了notify()方法,從結果我們可以看出只有一個執行緒被喚醒了,其他執行緒依然處於等待狀態,這時我們把notify()修改成notifyAll()方法,則執行結果如下:

begin wait() ThreadName=Thread-1
begin wait() ThreadName=Thread-2
begin wait() ThreadName=Thread-0
end wait() ThreadName=Thread-1
end wait() ThreadName=Thread-0
end wait() ThreadName=Thread-2

  

可以看到所有等待的執行緒都已經被釋放

 

4、生產者/消費者模式實現

等待/通知模式最經典的案例就是“生產者/消費者”模式。但此模式在使用上有幾種“變形”,還有一些小的注意事項,但原理都是基於wait/notify的。

(1)、一個生產者和一個消費者:操作值

共同操作的值:

public class ValueObject {
	
	public static String value="";

}

  

生產者:生產者生產東西

public class Product {
	
	private String lock;
	
	public Product(String lock) {
		this.lock=lock;
	}
	
	public void setValue() {
		try {
			synchronized (lock) {
				if(!ValueObject.value.equals("")) {
					lock.wait();
				}
				String value=System.currentTimeMillis()+"_"+System.nanoTime();
				System.out.println("set的值是"+value);
				ValueObject.value=value;
				lock.notify();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

消費者:消費者消費東西

public class Customer {
	
	private String lock;
	public Customer(String lock) {
		this.lock=lock;
	}
	
	public void getValue() {
		try {
			synchronized(lock) {
				if(ValueObject.value.equals("")) {
					lock.wait();
				}
				System.out.println("get的值是"+ValueObject.value);
				ValueObject.value="";
				lock.notify();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

測試程式碼:

class ThreadP extends Thread{
	private Product p;
	
	public ThreadP(Product p) {
		this.p=p;
	}
	
	@Override
	public void run() {
		while(true) {
			p.setValue();
		}
	}
}

class ThreadC extends Thread{
	private Customer c;
	
	public ThreadC(Customer c) {
		this.c=c;
	}
	
	@Override
	public void run() {
		while(true) {
			c.getValue();
		}
	}
}

public class Run {
	
	public static void main(String[] args) {
		String lock=new String("");
		Product p=new Product(lock);
		Customer c=new Customer(lock);
		ThreadP pThreadP=new ThreadP(p);
		ThreadC cThreadC=new ThreadC(c);
		pThreadP.start();
		cThreadC.start();
	}

}

  

部分執行結果:

set的值是1575270909669_589770446584700
get的值是1575270909669_589770446584700
set的值是1575270909669_589770446607900
get的值是1575270909669_589770446607900
set的值是1575270909669_589770446631300
get的值是1575270909669_589770446631300
set的值是1575270909669_589770446654500
get的值是1575270909669_589770446654500
set的值是1575270909669_589770446678200
get的值是1575270909669_589770446678200
set的值是1575270909669_589770446701400
get的值是1575270909669_589770446701400
set的值是1575270909669_589770446724800

  

此例項生產者生產一個產品,消費者消費一個產品,在程式碼中就是對ValueObject中的value值進行操作

 

(2)多生產與多消費:操作值

上一個示例只有一個生產者和一個消費者,但現實生活中通常不會只有一個,下面我們來看一下多生產多消費的情況

共同操作的值:

public class ValueObject {
	
	public static String value="";

}

  

生產者:

public class Product {
	
	private String lock;
	
	public Product(String lock) {
		this.lock=lock;
	}
	
	public void setValue() {
		try {
			synchronized (lock) {
				if(!ValueObject.value.equals("")) {
					System.out.println("生產者"+Thread.currentThread().getName()+"WATING了★");
					lock.wait();
				}
				System.out.println("生產者"+Thread.currentThread().getName()+"RUNNABLE了☆");
				String value=System.currentTimeMillis()+"_"+System.nanoTime();
				System.out.println("set的值是"+value);
				ValueObject.value=value;
				lock.notify();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

消費者:

public class Customer {
	
	private String lock;
	public Customer(String lock) {
		this.lock=lock;
	}
	
	public void getValue() {
		try {
			synchronized(lock) {
				if(ValueObject.value.equals("")) {
					System.out.println("消費者"+Thread.currentThread().getName()+"WATING了★");
					lock.wait();
				}
				System.out.println("消費者"+Thread.currentThread().getName()+"RUNNABLE了☆");
				System.out.println("get的值是"+ValueObject.value);
				ValueObject.value="";
				lock.notify();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

測試類:

import java.beans.ParameterDescriptor;

import javax.swing.text.rtf.RTFEditorKit;

class ThreadP extends Thread{
	private Product p;
	
	public ThreadP(Product p) {
		this.p=p;
	}
	
	@Override
	public void run() {
		while(true) {
			p.setValue();
		}
	}
}

class ThreadC extends Thread{
	private Customer c;
	
	public ThreadC(Customer c) {
		this.c=c;
	}
	
	@Override
	public void run() {
		while(true) {
			c.getValue();
		}
	}
}

public class Run {
	
	public static void main(String[] args) throws InterruptedException {
		String lock=new String("");
		Product p=new Product(lock);
		Customer c=new Customer(lock);
		ThreadP[] pThreadP=new ThreadP[2];
		ThreadC[] cThreadC=new ThreadC[2];
		for(int i=0;i<2;i++) {
			pThreadP[i]=new ThreadP(p);
			pThreadP[i].setName("生產者"+(i+1));
			cThreadC[i]=new ThreadC(c);
			cThreadC[i].setName("消費者"+(i+1));
			pThreadP[i].start();
			cThreadC[i].start();
		}
		Thread.sleep(5000);
		Thread[] threadArray=new Thread[Thread.currentThread().getThreadGroup().activeCount()];
		for(int i=0;i<threadArray.length;i++) {
			System.out.println(threadArray[i].getName()+" "+threadArray[i].getState());
		}
		
	}

}

  

部分執行結果:

生產者生產者2RUNNABLE了☆
生產者生產者2WATING了★
消費者消費者1RUNNABLE了☆
消費者消費者1WATING了★
生產者生產者1RUNNABLE了☆
生產者生產者1WATING了★
生產者生產者2RUNNABLE了☆
生產者生產者2WATING了★
消費者消費者1RUNNABLE了☆
消費者消費者1WATING了★
生產者生產者1RUNNABLE了☆
生產者生產者1WATING了★
生產者生產者2RUNNABLE了☆

  

上面的示例就是多個生產者和多個消費者的情況

但是有時候結果可能並沒有我們想象中的那麼美好,多生產者消費者可能會出現假死狀態,比如下面的情況:

生產者生產者1RUNNABLE了
生產者生產者1WAITING了★
生產者生產者2WAITING了★
消費者消費者2 RUNNABLE了
消費者消費者2 WAITING了☆
消費者消費者1WAITING了☆
生產者生產者1RUNNABLE了
生產者生產者1WAITING了★
生產者生產者2 WAITING了★
main RUNNABLE
生產者1WAITING
消費者1WAITING
生產者2 WAITING
消費者2 WAITING

  

我們來逐步分析一下為什麼會出現這樣的結果:

1)生產者1進行生產,生產完畢後發出通知(但此通知屬於“通知過早”),並釋放鎖,準備進入下一次的while迴圈。

2)生產者1進入了下一次while迴圈,迅速再次持有鎖,發現產品並沒有被消費,所以生產者1呈等待狀態。

3)生產者2被start()啟動,生產者2發現產品還沒有被消費,所以生產者2也呈等待狀態。

4)消費者2被start()啟動,消費者2持有鎖,將產品消費併發出通知(發出的通知喚醒了第7行生產者1),執行結束後釋放鎖,等待消費者2進入下次迴圈。

5)消費者2進入了下一次的while迴圈,並持有鎖,發現產品並未生產,所以釋放鎖並呈等待狀態。

6)消費者1被start()啟動,快速持有鎖,發現產品並未生產,所以釋放鎖並呈等待狀態。

7)由於消費者2在第4行已經將產品進行消費,喚醒了第7行的生產者1進行順利生產後釋放鎖,併發出通知(此通知喚醒了第9行的生產者2),生產者1準備進入下一次的while迴圈。

8)這時生產者1進人下一次的while迴圈再次持有鎖,發現產品還並未消費,所以生產者1也呈等待狀態。

9)由於第7行的生產者1喚醒了生產者2,生產者2發現產品還並未被消費,所以生產者2也呈等待狀態。

出現符號就代表本執行緒進入等待狀態,需要額外注意這樣的執行結果。

假死出現的主要原因是有可能連續喚醒同類。怎麼能解決這樣的問題呢?不光喚醒同類,將異類也一同喚醒就解決了。

 

解決假死問題:

解決假死問題其實很簡單,就是將Product類和Customer類中的notify()方法修改為notifyAll()方法即可,它的原理就是不光通知同類執行緒,也包括異類,這樣就不至於出現假死狀態了,程式會一直執行下去。

 

方法join的使用:

在很多情況下,主執行緒建立並啟動子執行緒,如果子執行緒中要進行大量的耗時運算,主執行緒往往將早於子執行緒結束之前結束。這時,如果主執行緒想等待子執行緒執行完成之後再結束,比如子執行緒處理一個數據,主執行緒要取得這個資料中的值,就要用到join()方法了。方法join()的作用是等待執行緒物件銷燬。

在介紹join之前我們先來看一段程式碼:

class MyThread extends Thread{
	@Override
	public void run() {
		try {
			int secondValue=(int)(Math.random()*10000);
			System.out.println(secondValue);
			Thread.sleep(secondValue);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}


public class Test{
	
	public static void main(String[] args) {
		MyThread threadTest=new MyThread();
		threadTest.start();
		//Thread.sleep(?);
		System.out.println("我想當threadTest物件執行完畢後在執行");
		System.out.println("但是上面程式碼中的sleep()中的值應該寫多少呢");
		System.out.println("答案是:根據不能確定");
	}

}

  

執行結果:

我想當threadTest物件執行完畢後在執行
但是上面程式碼中的sleep()中的值應該寫多少呢
答案是:根據不能確定
6309

  

上述程式碼想要通過sleep()方法實現當threadTest執行緒執行完畢後再執行主執行緒,但是我們並不知道threadTest執行緒會執行多長時間,所以這裡的sleep不知道該填寫多少。

 

1、用join()方法來解決:

class MyThread extends Thread{
	@Override
	public void run() {
		try {
			int secondValue=(int)(Math.random()*10000);
			System.out.println(secondValue);
			Thread.sleep(secondValue);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}


public class Test{
	
	public static void main(String[] args) {
		try {
			MyThread threadTest=new MyThread();
			threadTest.start();
			threadTest.join();
			System.out.println("我想當threadTest物件執行完畢後我在執行,我做到了");
			
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

執行結果:

2122
我想當threadTest物件執行完畢後我在執行,我做到了

  

方法join的作用是使所屬的執行緒物件x正常執行run()方法中的任務,而使當前執行緒z進行無限期的阻塞,等待執行緒x銷燬後再繼續執行執行緒z後面的程式碼。

方法join具有使執行緒排隊執行的作用,有些類似同步的執行效果。join與synchronized的區別是:join在內部使用wait()方法進行等待,而sychronized關鍵字使用的是“物件監視器”原理做為同步。

 

2、方法join(long)的使用:

方法join(long)中的引數是設定等待的時間

class MyThread extends Thread{
	@Override
	public void run() {
		try {
			System.out.println("begin Timer="+System.currentTimeMillis());
			Thread.sleep(5000);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}


public class Test01 {
	
	public static void main(String[] args) {
		try {
			MyThread thread=new MyThread();
			thread.start();
			thread.join(2000);
			System.out.println("end time="+System.currentTimeMillis());
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

執行結果:

begin Timer=1575277463900
end time=1575277465900

  

結果等待兩秒再執行,之前的部落格中我們還學習過一個方法可以讓執行緒暫停執行sleep(long),join和sleep方法都可以讓執行緒暫停,那麼兩者有什麼區別?

 

方法join(long)與sleep(long)的區別:

方法join(long)的功能在內部是使用wait(long)方法來實現的,所以join(long)方法具有釋放鎖的特點。

join方法的原始碼如下:

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

  

從原始碼中可以瞭解到,當執行wait(long)方法後,當前執行緒的鎖被釋放,那麼其他執行緒就可以呼叫此執行緒中的同步方法了。

 

類ThreadLocal的使用:

變數值的共享可以使用public static變數的形式,所有的執行緒都使用同一個public static變數。如果想實現每一個執行緒都有自己的共享變數該如何解決呢?JDK中提供的類ThreadLocal正是為了解決這樣的問題。
類ThreadLocal主要解決的就是每個執行緒繫結自己的值,可以將ThreadLocal類比喻成全域性存放資料的盒子,盒子中可以儲存每個執行緒的私有資料。

 

1、get()方法與null

public class Test01 {
	
	public static ThreadLocal t1=new ThreadLocal();
	public static void main(String[] args) {
		if(t1.get()==null) {
			System.out.println("從未放過值");
			t1.set("我的值");
		}
		System.out.println(t1.get());
		System.out.println(t1.get());
	}

}

  

執行結果:

從未放過值
我的值
我的值

  

類ThreadLocal解決的是變數在不同執行緒間的隔離性,也就是不同執行緒擁有自己的值,不同執行緒中的值是可以放入ThreadLocal中進行儲存的。

 

ThreadLocal執行緒變數間的隔離性:

class Tools{
	public static ThreadLocal t1=new ThreadLocal();
}

class ThreadE extends Thread{
	@Override
	public void run() {
		try {
			for(int i=0;i<10;i++) {
				Tools.t1.set("ThreadE"+(i+1));
				System.out.println("ThreadE get Value"+Tools.t1.get());
				Thread.sleep(200);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

class ThreadG extends Thread{
	@Override
	public void run() {
		try {
			for(int i=0;i<10;i++) {
				Tools.t1.set("ThreadG"+(i+1));
				System.out.println("ThreadG get Value"+Tools.t1.get());
				Thread.sleep(200);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

public class Test01 {
	
	public static void main(String[] args) {
		try {
			ThreadE e=new ThreadE();
			ThreadG g=new ThreadG();
			e.start();
			g.start();
			for(int i=0;i<10;i++) {
				Tools.t1.set("Main"+(i+1));
				System.out.println("Main get Value="+Tools.t1.get());
				Thread.sleep(200);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	

}

  

執行結果:

Main get Value=Main1
ThreadG get ValueThreadG1
ThreadE get ValueThreadE1
ThreadE get ValueThreadE2
ThreadG get ValueThreadG2
Main get Value=Main2
ThreadG get ValueThreadG3
ThreadE get ValueThreadE3
Main get Value=Main3
ThreadE get ValueThreadE4
ThreadG get ValueThreadG4
Main get Value=Main4
ThreadE get ValueThreadE5
ThreadG get ValueThreadG5
Main get Value=Main5
Main get Value=Main6
ThreadG get ValueThreadG6
ThreadE get ValueThreadE6
ThreadE get ValueThreadE7
ThreadG get ValueThreadG7
Main get Value=Main7
Main get Value=Main8
ThreadG get ValueThreadG8
ThreadE get ValueThreadE8
ThreadE get ValueThreadE9
Main get Value=Main9
ThreadG get ValueThreadG9
Main get Value=Main10
ThreadG get ValueThreadG10
ThreadE get ValueThreadE10

  

上面程式碼中共有三個執行緒,ThreadE,ThreadG和main執行緒,每個執行緒都在操作自己內部的資料,執行緒之間互不影響

&n