1. 程式人生 > >胡八一之Java(八):多執行緒

胡八一之Java(八):多執行緒

多執行緒的優勢:多程序執行需要獨立的記憶體空間,而多執行緒可以共享記憶體,從而提高了執行緒的執行效率。

建立執行緒一般使用兩種方式:

1、繼承Thread類:

import java.io.IOException;


public class Test extends Thread {
	private int i=0;
	public void run() {
		for(;i<100;i++) {
			System.out.println(this.getName()+":"+i);
		}
	}
 public static void main(String[] args) throws IOException {
    for(int i =0;i<100;i++) {
    	System.out.println(Thread.currentThread().getName()+" :"+i);
    	if(i == 20) {
    		//建立並啟動執行緒一
    		new Test().start();
    		//建立並啟動執行緒二
    		new Test().start();
    	}
    }
 }
}

2、通過實現Runnable介面:

import java.io.IOException;


public class Test implements Runnable {
	private int i=0;
	public void run() {
		for(;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
 public static void main(String[] args) throws IOException {
    for(int i =0;i<100;i++) {
    	System.out.println(Thread.currentThread().getName()+" :"+i);
    	if(i == 20) {
    		Test t =new Test();
    	   //通過new Thread(target,name)的方式來啟動執行緒
    		new Thread(t,"執行緒一").start();
    		new Thread(t,"執行緒二").start();
    	}
    }
 }
}

這兩種方式的區別:

通過繼承Thread類建立多執行緒,獲得執行緒物件比較簡單,通過this即可獲得,而通過實現Runnable介面建立多執行緒,需要通過Thread.currentThread()方法來獲得當前執行緒物件。前者建立Thread子類便可代表執行緒物件,後者建立的Runnable只能作為執行緒物件的target。

兩種方式的對比:

通過繼承Thread類建立執行緒較為簡單,但Java是單繼承,所以無法繼承其他類。

通過實現Runnable介面,如果需要訪問當前執行緒物件,需要通過Thread.currentThread()方法來訪問,但它優點就是可以繼承其他類,多執行緒共享一個target物件,非常適合多執行緒共享一份資源的情況。

因此,一般採用實現Runnable介面的方法實現多執行緒。

執行緒的生命週期:新建(new),就緒(Runnable),執行(Running),阻塞(Blocked),和死亡(Dead)

join執行緒:

當在某個程式中執行流呼叫其他執行緒的join()時,則當前執行緒被阻塞,知道被join()加入的join執行緒執行完畢以後。

import java.io.IOException;


public class Test implements Runnable {
	private int i=0;
	public void run() {
		for(;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
 public static void main(String[] args) throws IOException, InterruptedException {
    for(int i =0;i<100;i++) {
    	System.out.println(Thread.currentThread().getName()+" :"+i);
    	if(i == 20) {
    		Test t =new Test();
    	   //通過new Thread(target,name)的方式來啟動執行緒
    		Thread j1 =new Thread(t,"被jion的執行緒");
    		j1.start();
    		j1.join();
    		
    	}
    }
 }
}

執行部分結果:

main :18
main :19
main :20
被jion的執行緒:0
被jion的執行緒:1
被jion的執行緒:2
被jion的執行緒:3

可以看到,當主執行緒執行到20時被阻塞,在被join執行緒執行完後才會執行main執行緒。

後臺執行緒:

有一種執行緒,叫做“守護執行緒”,為其他執行緒提供服務,被稱為後臺執行緒。JVM的垃圾回收機制就是典型的後臺執行緒。

特徵:當前臺執行緒死亡後,後臺執行緒將隨之死亡。

呼叫Thread物件的setDaemon(true)方法可將指定執行緒設定為後臺執行緒。Thread類還提供了一個isDaemon()方法來判斷是否為後臺執行緒。

下面程式展示了當前臺執行緒執行完畢死亡後,後臺執行緒也隨之死亡。

import java.io.IOException;


public class Test implements Runnable {
	private int i=0;
	public void run() {
		for(;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
 public static void main(String[] args) throws IOException, InterruptedException {
   
 
    	Test t =new Test();
    	Thread b =new Thread(t,"後臺執行緒");
    	//將其設定為後臺執行緒
    	b.setDaemon(true);
    	//啟動後臺執行緒
    	b.start();
    	for(int i =0;i<10;i++) {
    	
    	   	System.out.println(Thread.currentThread().getName()+" :"+i);
    	}
    }
 }

執行結果:

main :0
main :1
main :2
main :3
main :4
後臺執行緒:0
main :5
後臺執行緒:1
後臺執行緒:2
main :6
main :7
main :8
main :9
後臺執行緒:3
後臺執行緒:4
後臺執行緒:5
後臺執行緒:6
後臺執行緒:7
後臺執行緒:8
後臺執行緒:9
後臺執行緒:10

執行緒睡眠:sleep

如果需要讓當前執行緒暫停一段時間,讓出cpu資源,並進入阻塞狀態,則可以通過呼叫Thread類的靜態sleep()方法實現。

程式如下:

import java.io.IOException;


public class Test implements Runnable {
	private int i=0;
	public void run() {
		for(;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
 public static void main(String[] args) throws IOException, InterruptedException {
   
 
    	Test t =new Test();
    	Thread b =new Thread(t,"執行緒一");
        //啟動執行緒
    	b.start();
    	for(int i =0;i<10;i++) {
    	    
    	   	System.out.println(Thread.currentThread().getName()+" :"+i);
    	   	if(i ==5) {
    	   		Thread.sleep(1000);
    	   	}
    	}
    }
 }

執行緒讓步:yield

上述的sleep()方法會將當前執行緒進入阻塞狀態,但yield()方法不會阻塞該執行緒,會將其轉入就緒狀態。yield()只是當前執行緒暫停一下,讓系統的執行緒排程器重新排程一次,完全可能的情況是:執行緒排程器又將其排程出來執行。

import java.io.IOException;


public class Test implements Runnable {
	private int i=0;

	public void run() {
		for(;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
			if(i==20) {
				Thread.yield();
			}
		}
	}
 public static void main(String[] args) throws IOException, InterruptedException {
   
 
    	Test t =new Test();
    	Thread b =new Thread(t,"高階執行緒");
    	//將執行緒設定為最高優先順序
    	//b.setPriority(Thread.MAX_PRIORITY);
        //啟動執行緒
    	b.start();
    	Thread c = new Thread(t,"低階執行緒");
    	//將執行緒設定為最低優先順序
    	//c.setPriority(Thread.MIN_PRIORITY);
    	//啟動執行緒
    	c.start();
    
    }
 }

yield()靜態方法在沒有設定優先順序的情況下會讓其排程器重新排程,在設定了優先順序的情況下,只能排程其同等優先順序或者更高優先順序的執行緒。

關於sleep()方法和yield()方法的區別:

sleep()方法暫停當前執行緒後,會不理會其他執行緒的優先順序,讓其他執行緒獲得執行機會。但yield()只會排程其同等優先順序或者更高優先順序的執行緒。

sleep()方法讓執行緒轉入阻塞狀態,直到經歷完阻塞時間後才會進入就緒狀態,而yield()強制讓當前執行緒進入就緒狀態,所以完全有可能呼叫的還是當前執行緒。

sleep()方法聲明瞭InteruptedExecption異常,所以呼叫該方法時要麼捕捉該異常,要麼顯式丟擲該異常;而yield()方法則沒有宣告丟擲任何異常。

sleep()方法比yield()有更好的移植性,所以不建議使用yield()方法來控制併發執行緒的執行。