java 執行緒基礎篇,看這一篇就夠了。
前言:
Java三大基礎框架:集合,執行緒,io基本是開發必用,面試必問的核心內容,今天我們講講執行緒。
想要把執行緒理解透徹,這需要具備很多方面的知識和經驗,本篇主要是關於執行緒基礎包括執行緒狀態和常用方法。
本篇主要從執行緒常用方法來理解執行緒各個狀態及狀態的切換,之後再通過狀態於狀態之間的切換來加深對執行緒常用方法的應用於印象。
正題:
java中定義了執行緒的幾種狀態,在java.lang.Thread.State中,分別為以下6個:
NEW(初始化),RUNNABLE(就緒),BLOCKED(阻塞),WAITING(等待),TIMED_WAITING(超時等待),TERMINATED(終止)
1. 建立:通過繼承Thread類,或者實現Runnable介面來建立一個執行緒。
方式1:繼承Java.lang.Thread類,並覆蓋run() 方法。
優勢:編寫簡單;
劣勢:單繼承的限制----無法繼承其它父類,同時不能實現資源共享。
方式2:實現Java.lang.Runnable介面,並實現run()方法。
優勢:可繼承其它類,多執行緒可共享同一個Thread物件;
劣勢:程式設計方式稍微複雜,如需訪問當前執行緒,需呼叫Thread.currentThread()方法
public class Daqiu extends Thread{ @Overridepublic void run() { System.out.println("我打完球了"); } } public class chifan implements Runnable{ @Override public void run() { System.out.println("我吃完了"); } }
2.就緒:當執行緒呼叫start()方法,會進入準備就緒狀態。
start方法是Thread 類的方法,在這個方法中會呼叫native方法(start0())來讓執行緒就緒,等待CPU為該執行緒分配資源(時間片
3.執行:當執行緒獲得cpu資源(時間片)會執行run()達到正真執行的效果,並可以呼叫yield()方法試探性的讓出cpu執行器權。
1) 上面說了,start()方法最終呼叫了一個native的方法,並非java實現的,所以這裡的run()方法是如何被呼叫的我們就不研究了。
2) yield()方法是對排程器的一個暗示表示願意讓出CPU執行器的當前使用權,但是排程器可以自由忽略這個提示。此方法使用極少,不過多研究。
3) 要注意,我們手動呼叫執行緒的run()方法也會執行run()方法的內容,但是這樣就沒有達到多執行緒的效果。這也是run()方法和start()方法的一個重要區別。通過下面的示例可以看出輸出的執行緒名不一樣。
public class TestThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName()); } } public static void main(String[] args){
TestThread test = new TestThread();
test.start();
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.run();
}
結果:
Thread-0
main
Process finished with exit code 0
4.超時等待:又稱為計時等待,讓執行緒等待一定時間。
1)sleep(long): 讓當前執行緒睡眠一定時間後再繼續執行,此時只釋放資源,不釋放鎖。這是一個靜態的native方法,不過多研究。
2)join(): 該方法有三個過載join(),join(long millis),join(long millis,int nanoseconds),主要看第二個,就是等待一個執行緒指定毫秒數後再執行。無引數的join方法其實就是呼叫了join(0),即永遠等待下去。不過通過原始碼我們可以看到,在while迴圈中有一個條件判斷,即isAlive()方法,意思是如果當前執行緒還活著,就會一直等待下去。
public static native void sleep(long millis) throws InterruptedException; public final void join() throws InterruptedException { join(0); }
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; } } }
對於join()方法的理解來看一個示例:
public class Chifan extends Thread{ private Daqiu daqiu; public Chifan(String name,Daqiu daqiu){ super(name); this.daqiu = daqiu; } @Override public void run() { try { daqiu.join(); System.out.println(getName()+"我開始吃飯。。。"); sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName()+"我吃完了"); } }
public class Daqiu extends Thread { private int playTime; public Daqiu(String name,int playTime) { super(name); this.playTime = playTime; } @Override public void run() { System.out.println(getName()+"我開始打球了"); try { sleep(playTime); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName()+"我打完球了"); } }
public static void main(String[] args){ Daqiu daqiu = new Daqiu("打球執行緒:",1000); Chifan chifan = new Chifan("吃飯執行緒:",daqiu); System.out.println("打完球才能吃飯"); daqiu.start(); chifan.start(); }
打完球才能吃飯
打球執行緒:我開始打球了
打球執行緒:我打完球了
吃飯執行緒:我開始吃飯。。。
吃飯執行緒:我吃完了
Process finished with exit code 0
可以看到:daqiu.join();一定是等打完球了才會執行後面的吃飯。
5.等待:wait()方法只能在synchronized中呼叫,因為前提是已經擁有某物件鎖,但是選擇暫時交出去,此時執行緒將進入等待佇列。
Object類中有三個不同引數的wait()方法,如果傳入時間引數,也可以理解為計時等待,但於sleep()不同的是wait()方法會釋放擁有的鎖,當被其他持有該鎖的執行緒呼叫notify()或notifyAll()喚醒時將進入同步佇列去競爭鎖。wait()實際也是呼叫了wait(long)方法,引數為0,表示一直等待下去。
6.阻塞:當synchronized(Obj)去競爭一個物件鎖時,如果物件鎖被其他執行緒佔用,那麼執行緒將進入等待佇列(阻塞)
在java中的Object型別中,都是帶有一個記憶體鎖的,在有執行緒獲取該記憶體鎖後,其它執行緒無法訪問該記憶體,從而實現JAVA中簡單的同步、互斥操作。當出現阻塞鎖定需要等其他持有該物件鎖的執行緒呼叫wait(),notify()或notifyAll()釋放物件鎖後才有機會獲取鎖進入執行狀態,所以wait(),notify()或notifyAll()只能在synchronized獲取到物件鎖內使用,這於第五點是相呼應的。(注意:呼叫notify()方法後,當前執行緒並不會馬上釋放該物件鎖,要等到執行notify()方法的執行緒執行完才會釋放物件鎖)
7.終止:當執行緒run()方法正常執行完畢,或者出現未捕獲的異常,執行緒就已經完成了他的使命進入終止狀態了。
瞭解了執行緒的6個狀態以及常用方法後,再來通過一個示例看看執行緒執行切換的一個流程。 思路就是:建立一個Student物件,三個執行緒A,B,C,且各自獲取Student物件鎖,其中A,B呼叫wait()方法釋放鎖進入等待佇列。C執行緒呼叫notify()方法喚醒等待佇列中的執行緒,public class TestThread extends Thread { private Student student; public TestThread(String name,Student student){ super(name); this.student = student; } @Override public void run() { synchronized (student){ try { System.out.println("我是執行緒:"+Thread.currentThread().getName()); student.wait(); System.out.println("我是執行緒:"+Thread.currentThread().getName()+",我拿到了物件鎖"); System.out.println("我是執行緒:"+Thread.currentThread().getName()+",我執行完了"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class NotifyThread extends Thread { private Student student; public NotifyThread(String name,Student student){ super(name); this.student = student; } @Override public void run() { synchronized (student){ System.out.println("我是喚醒執行緒"+Thread.currentThread().getName()); student.notifyAll(); System.out.println("我是喚醒執行緒"+Thread.currentThread().getName()+",我已經執行喚醒。"); } } }
public static void main(String[] args) throws InterruptedException { Student student = new Student("小明","12"); TestThread testA = new TestThread("A",student); TestThread testB = new TestThread("B",student); NotifyThread testC = new NotifyThread("C",student); testA.start(); testB.start(); sleep(10); testC.start(); }
我是執行緒:A
我是執行緒:B
我是喚醒執行緒C
我是喚醒執行緒C,我已經執行喚醒。
我是執行緒:B,我拿到了物件鎖
我是執行緒:B,我執行完了
我是執行緒:A,我拿到了物件鎖
我是執行緒:A,我執行完了
Process finished with exit code 0
延申一個執行緒面試中的經典問題:建立3個執行緒,順序答應‘A’,‘B’,‘C’10次
其實思路也是利用wait()和notify()來控制執行緒的執行順序,具體實現這裡就不說了。
總結:
java中執行緒還有非常多的延申,包括執行緒底層實現,多執行緒同步,併發,安全,喚醒機制,以及執行緒池,要學習於沉澱的知識非常多。希望本文能讓自己對執行緒的基礎有個清晰的理解。
------------恢復內容結束------------