1. 程式人生 > >Java高併發學習(一)

Java高併發學習(一)

Java高併發學習()

初始執行緒:執行緒的基本操作

進行java併發設計的第一步,就是必須瞭解Java虛擬機器提供的執行緒操作的API。比如如何建立並啟動執行緒,如何終止執行緒,中斷執行緒等。

1.定義執行緒:

(1).繼承Thread方法,形式如下

	public static class T1 extends Thread{
		@Override
		public void run(){
			System.out.println("fist.T1.run()");
		}
	}

(2) .實現Runnable介面建立執行緒方式,形式如下

class MyThread implements Runnable{
	@Override
	public void run(){
		System.out.println("fist.T2.run()");
	}
}
public class fist {
	public static void main(String args[]){
		MyThread mythread = new MyThread();
		Thread t1 = new Thread(mythread);
		t1.start();
	} 
}

說明:Thread類有一個非常重要的構造方法public Thread(Runnable target),他傳入一個Runnable例項,在start()方法呼叫時,新的執行緒會執行Runnable.run()方法。

實際上,預設的Thread.run()就是這麼做的:

Public void run(){
   if(target != null){
      Target.run();
   }
}

2.啟動執行緒:

啟動執行緒很簡單。只需要使用new關鍵字建立一個執行緒物件,並且將它start()起來即可。

Thread t1 = new Thread();
t1.start();

注意一下:

下面的程式碼能通過編譯,也能正常執行。但是,卻不能建立一個新的執行緒,而是在當前執行緒中呼叫

run()方法,只是作為一個普通方法去呼叫。

Thread t1 = new Thread();
t1.run();

因此希望大家特別注意,呼叫start()方法和run()方法的區別。

3.終止執行緒:

如何正常的關閉一個執行緒呢?查閱JDK,不難發現Thread提供了一個stop方法。如果使用stop()方法,就可以立即將一個執行緒終止,非常方便。但是eclipse會提示你stop()方法為廢棄方法。也就是說,在將來JDK會刪除該方法。

為什麼stop()方法會被廢棄呢?原因是stop()方法太過暴力,強行把在執行的執行緒停止,可能會導致資料不一致問題。

那如果需要停止一個執行緒時,應該怎麼做呢?其實方法很簡單,只是需要由我們自行決定執行緒何時退出就可以了。

例如:

class MyThread extends Thread{
	volatile boolean stopme = false;
	public void stopeMe(){
		stopme = true;
	}
	@Override
	public void run(){
		while(true){
			if(stopme){
				System.out.println("exit by stop me");
				break;
			}
		}
	}
}

程式碼中定義了一個標記變數stopme,用於指示執行緒是否需要退出。當stopMe()方法被呼叫,stopme被設定為true,此時,執行緒會檢測到這個改動,執行緒就自然退出了。

4.執行緒中斷:

從表面上理解,中斷就是讓執行緒暫停執行的意思,實際上並非如此。在上一節中我們已經討論了stop()方法停止執行緒的害處,並且提供了一套完善執行緒退出功能。那在JDK中是否提供更強大的支援呢?答案是肯定的,那就是執行緒中斷。

與執行緒中斷有關的有三個方法,這三個方法看起來很像,所以可能會引起混淆和誤用,請大家注意。

public void Thread.interrupt()   //中斷執行緒
Public void boolean Thread.isInterrupted()   //判斷執行緒是否被中斷
Public static boolean Thread.interrupted()   //判斷是否被中斷,並請除當前中斷狀態

Thread.interrupt()設定中斷標誌位,Thread.isInterrupted()檢查中斷標誌位,Thread.interrupted()清除中斷標誌位。

下面這段程式碼對t1進行了中斷,那麼t1中斷後,t1會停止執行嗎?

class MyThread extends Thread{
	@Override
	public void run(){
		while(true){
			System.out.println("Thread running");
		}
	}
}

public class fist{
	public static void main(String args[]){
		Thread t1 = new MyThread();
		t1.start();
		t1.interrupt();
	}
}

在這裡雖然會t1進行了中斷,但是t1並沒處理中斷的邏輯,因此,即使t1執行緒被設定了中斷,但是這個中斷不會發生任何作用。

如果希望t1在中斷後退出,就必須為他增加相應的中斷處理程式碼:

class MyThread extends Thread{
	@Override
	public void run(){
		while(true){
			System.out.println("Thread running");
			if(Thread.currentThread().isInterrupted()){
				System.out.println("interrput");
				break;
			}
		}
	}
}

public class fist{
	public static void main(String args[]) throws InterruptedException{
		Thread t1 = new MyThread();
		t1.start();
		Thread.sleep(2000);
		t1.interrupt();
	}
}

特別注意,如果在迴圈體中出現了wait()或者sleep()這樣操作的時候,中斷可能會被忽略。

Thread.sleep()方法會讓當前執行緒休眠若干時間,他會丟擲一個interruptException中斷異常。interruptException不是執行時異常,也就是程式必須捕獲並處理他。當執行緒在休眠時,如果被中斷,這個異常就會產生。

class MyThread extends Thread{
	@Override
	public void run(){
		while(true){
			System.out.println("Thread running");
			if(Thread.currentThread().isInterrupted()){
				System.out.println("interrput");
				break;
			}
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				System.out.println("when sleep interrupt");
				Thread.currentThread().interrupt();
			}
			System.out.println("Thread end");
		}
	}
}

public class fist{
	public static void main(String args[]) throws InterruptedException{
		Thread t1 = new MyThread();
		t1.start();
		Thread.sleep(1000);
		t1.interrupt();
	}
}

如果執行緒執行到了sleep()程式碼段,主程式中斷執行緒,執行緒這這時候丟擲異常,進入catch的異常處理。在catch程式碼段中,由於捕獲到了中斷,我們可以立即退出執行緒。在這裡我們並沒有這麼做,因為也許在這段程式碼中,我們還必須進行後續處理,保證資料的一致性和完整性,因此,執行了Thread.interrupt()方法在次中斷自己,設定中斷標誌位。只有這麼做才能當執行緒休眠時響應中斷。

注意:Thread.sleep()方法由於中斷而丟擲異常,此時,它會清除中斷標誌位,如果不加處理,那麼在下一次迴圈開始前,就無法捕獲這個中斷,故在異常處理中,在次設定中斷標記位。

5.等待(wait)和通知(notify)

為了支援多執行緒之間的協作,JDK提供了兩個非常重要的介面,執行緒等待wait()方法和執行緒通知方法notify()。這兩個方法不是在Thread類中的,而是Object類的。這也意味著任何物件都能呼叫者這兩種方法。

public final void wait() throws InterruptException
public final native void notify()

wait()notify()是怎麼工作的呢?如果一個執行緒呼叫了object.wait()方法,那麼他就會進入object物件的等待佇列。這個佇列中可能會有多個等待執行緒,因為系統運行同時等待某個物件。當object.notify()被呼叫時,他就會從這個等待佇列中隨機選擇一個執行緒喚醒。這裡希望大家注意,這個喚醒過程是不公平的,並不是先等待的執行緒會優先選擇。

除了object.notify()之外還有object.notifyAll()方法,他會喚醒等待佇列中的所有執行緒。

這裡還需要注意一點,object.wait()方法和object.notify()方法並不是可以隨便呼叫的。他必須包含在對應的synchronized語句中,無論是wait還是notify都需要首先獲得目標物件的一個監視器。

這裡給出簡單實用waitnotify的案例:


import java.util.Objects;
public class fist{
	final static Object object = new Object();
	
	public static class MyThread_1 extends Thread{
		@Override
		public void run(){
			synchronized (object) {
				System.out.println(System.currentTimeMillis()+"T1 start");
				try {
					System.out.println(System.currentTimeMillis()+"T1 wait");
					object.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(System.currentTimeMillis()+"T1 end");
			}
		}
	}
	public static class MyThread_2 extends Thread{
		@Override
		public void run(){
			synchronized (object) {
				System.out.println(System.currentTimeMillis()+"T2 start and notify");
				object.notify();
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	public static void main(String args[]){
		Thread t1 = new MyThread_1();
		Thread t2 = new MyThread_2();
		t1.start();
		t2.start();
	}
}

上述程式碼中,開啟了兩個執行緒t1t2t1執行了object.wait()方法。注意,在wait方法執行前,t1申請了object物件鎖。因此在執行object.wait()時,他說持有鎖的。Wait()方法執行後,t1會進行等待,並且釋放object物件的鎖。t2在執行notify之前也會獲得object物件鎖,在notify執行後釋放object物件的鎖。

輸出結果如下:


6.掛起(suspend)和繼續執行(resume)

這兩個操作是一對相反的操作,被掛起的執行緒必須等到resume()操作後才能繼續執行。但如果仔細閱讀文件後會發現,他們早已被標註為廢棄方法,不推薦使用。

不推薦使用suspend()去掛起執行緒的原因,是因為suspend()在導致執行緒暫停的同時,並不會去釋放任何資源鎖。此時,任何執行緒想訪問它佔用的鎖時,都會被牽連,無法正常執行。

如果,resume操作意外的再suspend前就執行了,那麼被掛起的執行緒就很難有機會繼續執行了。並且更嚴重的是他的鎖並不會被釋放,因此可能會導致整個系統無法正常工作。而且,對於被掛起的執行緒,從執行緒狀態上看,居然還是runnable,這也會嚴重的影響到我們對系統當前狀態的判斷。

import java.util.Objects;
public class fist{
	final static Object object = new Object();
	
	public static class MyThread extends Thread{
		public MyThread(String name){
			super.setName(name);
		}
		@Override
		public void run(){
			synchronized (object) {
				System.out.println("in "+getName());
				Thread.currentThread().suspend();
			}
                        System.out.println("end "+getName());
		}
	}
	public static void main(String args[]) throws InterruptedException{
		Thread t1 = new MyThread("t1");
		Thread t2 = new MyThread("t2");
		t1.start();
		Thread.sleep(2000);
		t2.start();
		t1.resume();
		t2.resume();
	}
}

輸出:



這段程式碼的程式不會退出。而是會掛起。使用jstack命令列印系統執行緒資訊可以看到。

這時我們需要注意,當前系統中,t2其實是被掛起的。但是他的執行緒狀態是runnable,這很有可能對導致我們誤判當前系統的狀態。同時,雖然主程式已經呼叫了resume(),但是由於時間先後順序的緣故,resume並沒有生效!這就導致了執行緒t2被永久掛起,並且永久佔用了物件object的鎖。這對於系統來說極有可能是致命的。

沒有輸出”end t2”的原因是t2.resume()t2.suspend()前就運行了,導致t2永遠被掛起,如果把程式碼改寫成如下,才會輸出”end t2”。

import java.util.Objects;

public class fist{
	final static Object object = new Object();
	
	public static class MyThread extends Thread{
		public MyThread(String name){
			super.setName(name);
		}
		@Override
		public void run(){
			synchronized (object) {
				System.out.println("in "+getName());
				Thread.currentThread().suspend();
			}
			System.out.println("end "+getName());
		}
	}
	public static void main(String args[]) throws InterruptedException{
		Thread t1 = new MyThread("t1");
		Thread t2 = new MyThread("t2");
		t1.start();
		Thread.sleep(2000);   //設定延遲
		t2.start();
		t1.resume();
		t2.resume();
	}
}

輸出:




7.等待執行緒結束(join)和謙讓(yield)

很多時候,一個執行緒的輸入可能會非常依賴另一個或多個執行緒的輸出,此時,這個執行緒就需要等待依賴執行緒的執行完畢,才能繼續執行。JDK提供了join()操作來實現這個功能,如下所示,顯示了兩個join()方法:

public final void join() throws InterruptedException

public final synchronized void join() throws InterruptedException

第一個join方法表示無限等待,他會一直阻塞執行緒,直到目標執行緒執行完畢。第二個給出了一個最大等待時間,如果超過給定時間目標執行緒還在執行,當前執行緒也會“等不及了”,而繼續往下執行。

這裡提供一個簡單的join例項,以供參考:

import java.util.Objects;

public class fist{
	public volatile static int  i = 0;
	public static class MyThread extends Thread{
		@Override
		public void run(){
			for(i=0;i<10000000;i++);
		}
	}
	
	public static void main(String args[]) throws InterruptedException{
		Thread t = new MyThread();
		t.start();
		t.join();
		System.out.println(i);
	}
}

主函式中,如果不使用join等待MyThread執行緒,那麼得到的i可能是0或者是一個很小的數值。因為MyThread還沒開始執行,i的值就被打印出來了。但在使用join方法後,表示主執行緒願意等待MytThread執行緒執行完畢,跟著MyThread一起往前走,故在join返回時,MyThread已經執行完畢,故i總是10000000

另外一個比較有趣的方法,Thread.yield()

這是一個靜態方法,一旦執行,它會使當前執行緒讓出cpu。但要注意讓出cpu不代表不在執行了。當前執行緒在讓出cpu後,還會進行cpu的資源爭奪,但是能否被在次分配到,就不一定了。

如果你覺得一個執行緒不那重要,或者優先順序非常低,而且又害怕它會佔用太多的cpu資源,那麼可以在適當的時候呼叫yield,給予其他重要執行緒更多工作機會。