胡八一之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()方法來控制併發執行緒的執行。