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線程,每個線程都在操作自己內部的數據,線程之間互不影響

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM