[轉]Java多線程乾貨系列—(一)Java多線程基礎

Java多線程乾貨系列—(一)Java多線程基礎

字數7618 閱讀1875 評論21 

前言

多線程併發編程是Java編程中重要的一塊內容,也是面試重點覆蓋區域,因此學好多線程併發編程對咱們來講極其重要,下面跟我一塊兒開啓本次的學習之旅吧。html

正文

線程與進程

1 線程:進程中負責程序執行的執行單元
線程自己依靠程序進行運行
線程是程序中的順序控制流,只能使用分配給程序的資源和環境java

2 進程:執行中的程序
一個進程至少包含一個線程面試

3 單線程:程序中只存在一個線程,實際上主方法就是一個主線程編程

4 多線程:在一個程序中運行多個任務
目的是更好地使用CPU資源緩存

線程的實現

繼承Thread類

java.lang包中定義, 繼承Thread類必須重寫run()方法安全

class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主動建立的第"+num+"個線程"); } }

建立好了本身的線程類以後,就能夠建立線程對象了,而後經過start()方法去啓動線程。注意,不是調用run()方法啓動線程,run方法中只是定義須要執行的任務,若是調用run方法,即至關於在主線程中執行run方法,跟普通的方法調用沒有任何區別,此時並不會建立一個新的線程來執行定義的任務。多線程

public class Test { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } } class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主動建立的第"+num+"個線程"); } }

在上面代碼中,經過調用start()方法,就會建立一個新的線程了。爲了分清start()方法調用和run()方法調用的區別,請看下面一個例子:併發

public class Test { public static void main(String[] args) { System.out.println("主線程ID:"+Thread.currentThread().getId()); MyThread thread1 = new MyThread("thread1"); thread1.start(); MyThread thread2 = new MyThread("thread2"); thread2.run(); } } class MyThread extends Thread{ private String name; public MyThread(String name){ this.name = name; } @Override public void run() { System.out.println("name:"+name+" 子線程ID:"+Thread.currentThread().getId()); } }

運行結果:框架

從輸出結果能夠得出如下結論:ide

1)thread1和thread2的線程ID不一樣,thread2和主線程ID相同,說明經過run方法調用並不會建立新的線程,而是在主線程中直接運行run方法,跟普通的方法調用沒有任何區別;

2)雖然thread1的start方法調用在thread2的run方法前面調用,可是先輸出的是thread2的run方法調用的相關信息,說明新線程建立的過程不會阻塞主線程的後續執行。

實現Runnable接口

在Java中建立線程除了繼承Thread類以外,還能夠經過實現Runnable接口來實現相似的功能。實現Runnable接口必須重寫其run方法。
下面是一個例子:

public class Test { public static void main(String[] args) { System.out.println("主線程ID:"+Thread.currentThread().getId()); MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); } } class MyRunnable implements Runnable{ public MyRunnable() { } @Override public void run() { System.out.println("子線程ID:"+Thread.currentThread().getId()); } }

Runnable的中文意思是「任務」,顧名思義,經過實現Runnable接口,咱們定義了一個子任務,而後將子任務交由Thread去執行。注意,這種方式必須將Runnable做爲Thread類的參數,而後經過Thread的start方法來建立一個新線程來執行該子任務。若是調用Runnable的run方法的話,是不會建立新線程的,這根普通的方法調用沒有任何區別。

事實上,查看Thread類的實現源代碼會發現Thread類是實現了Runnable接口的。

在Java中,這2種方式均可以用來建立線程去執行子任務,具體選擇哪種方式要看本身的需求。直接繼承Thread類的話,可能比實現Runnable接口看起來更加簡潔,可是因爲Java只容許單繼承,因此若是自定義類須要繼承其餘類,則只能選擇實現Runnable接口。

使用ExecutorService、Callable、Future實現有返回結果的多線程

多線程後續會學到,這裏暫時先知道一下有這種方法便可。

ExecutorService、Callable、Future這個對象實際上都是屬於Executor框架中的功能類。想要詳細瞭解Executor框架的能夠訪問http://www.javaeye.com/topic/366591,這裏面對該框架作了很詳細的解釋。返回結果的線程是在JDK1.5中引入的新特徵,確實很實用,有了這種特徵我就不須要再爲了獲得返回值而大費周折了,並且即使實現了也可能漏洞百出。

可返回值的任務必須實現Callable接口,相似的,無返回值的任務必須Runnable接口。執行Callable任務後,能夠獲取一個Future的對象,在該對象上調用get就能夠獲取到Callable任務返回的Object了,再結合線程池接口ExecutorService就能夠實現傳說中有返回結果的多線程了。下面提供了一個完整的有返回結果的多線程測試例子,在JDK1.5下驗證過沒問題能夠直接使用。代碼以下:

/** * 有返回值的線程 */ @SuppressWarnings("unchecked") public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("----程序開始運行----"); Date date1 = new Date(); int taskSize = 5; // 建立一個線程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 建立多個有返回值的任務 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 執行任務並獲取Future對象 Future f = pool.submit(c); // System.out.println(">>>" + f.get().toString()); list.add(f); } // 關閉線程池 pool.shutdown(); // 獲取全部併發任務的運行結果 for (Future f : list) { // 從Future對象上獲取任務的返回值,並輸出到控制檯 System.out.println(">>>" + f.get().toString()); } Date date2 = new Date(); System.out.println("----程序結束運行----,程序運行時間【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } } class MyCallable implements Callable<Object> { private String taskNum; MyCallable(String taskNum) { this.taskNum = taskNum; } public Object call() throws Exception { System.out.println(">>>" + taskNum + "任務啓動"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任務終止"); return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】"; } }

代碼說明:
上述代碼中Executors類,提供了一系列工廠方法用於創先線程池,返回的線程池都實現了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
建立固定數目線程的線程池。

public static ExecutorService newCachedThreadPool()
建立一個可緩存的線程池,調用execute 將重用之前構造的線程(若是線程可用)。若是現有線程沒有可用的,則建立一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。

public static ExecutorService newSingleThreadExecutor()
建立一個單線程化的Executor。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
建立一個支持定時及週期性的任務執行的線程池,多數狀況下可用來替代Timer類。

ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,返回Future。若是Executor後臺線程池尚未完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。

線程的狀態

在正式學習Thread類中的具體方法以前,咱們先來了解一下線程有哪些狀態,這個將會有助於後面對Thread類中的方法的理解。

  • 建立(new)狀態: 準備好了一個多線程的對象
  • 就緒(runnable)狀態: 調用了start()方法, 等待CPU進行調度
  • 運行(running)狀態: 執行run()方法
  • 阻塞(blocked)狀態: 暫時中止執行, 可能將資源交給其它線程使用
  • 終止(dead)狀態: 線程銷燬

當須要新起一個線程來執行某個子任務時,就建立了一個線程。可是線程建立以後,不會當即進入就緒狀態,由於線程的運行須要一些條件(好比內存資源,在前面的JVM內存區域劃分一篇博文中知道程序計數器、Java棧、本地方法棧都是線程私有的,因此須要爲線程分配必定的內存空間),只有線程運行須要的全部條件知足了,才進入就緒狀態。

當線程進入就緒狀態後,不表明馬上就能獲取CPU執行時間,也許此時CPU正在執行其餘的事情,所以它要等待。當獲得CPU執行時間以後,線程便真正進入運行狀態。

線程在運行狀態過程當中,可能有多個緣由致使當前線程不繼續運行下去,好比用戶主動讓線程睡眠(睡眠必定的時間以後再從新執行)、用戶主動讓線程等待,或者被同步塊給阻塞,此時就對應着多個狀態:time waiting(睡眠或等待必定的事件)、waiting(等待被喚醒)、blocked(阻塞)。

當因爲忽然中斷或者子任務執行完畢,線程就會被消亡。

下面這副圖描述了線程從建立到消亡之間的狀態:

在有些教程上將blocked、waiting、time waiting統稱爲阻塞狀態,這個也是能夠的,只不過這裏我想將線程的狀態和Java中的方法調用聯繫起來,因此將waiting和time waiting兩個狀態分離出來。

注:sleep和wait的區別:

  • sleepThread類的方法,waitObject類中定義的方法.
  • Thread.sleep不會致使鎖行爲的改變, 若是當前線程是擁有鎖的, 那麼Thread.sleep不會讓線程釋放鎖.
  • Thread.sleepObject.wait都會暫停當前的線程. OS會將執行時間分配給其它線程. 區別是, 調用wait後, 須要別的線程執行notify/notifyAll纔可以從新得到CPU執行時間.

上下文切換

對於單核CPU來講(對於多核CPU,此處就理解爲一個核),CPU在一個時刻只能運行一個線程,當在運行一個線程的過程當中轉去運行另一個線程,這個叫作線程上下文切換(對於進程也是相似)。

因爲可能當前線程的任務並無執行完畢,因此在切換時須要保存線程的運行狀態,以便下次從新切換回來時可以繼續切換以前的狀態運行。舉個簡單的例子:好比一個線程A正在讀取一個文件的內容,正讀到文件的一半,此時須要暫停線程A,轉去執行線程B,當再次切換回來執行線程A的時候,咱們不但願線程A又從文件的開頭來讀取。

所以須要記錄線程A的運行狀態,那麼會記錄哪些數據呢?由於下次恢復時須要知道在這以前當前線程已經執行到哪條指令了,因此須要記錄程序計數器的值,另外好比說線程正在進行某個計算的時候被掛起了,那麼下次繼續執行的時候須要知道以前掛起時變量的值時多少,所以須要記錄CPU寄存器的狀態。因此通常來講,線程上下文切換過程當中會記錄程序計數器、CPU寄存器狀態等數據。

說簡單點的:對於線程的上下文切換實際上就是 存儲和恢復CPU狀態的過程,它使得線程執行可以從中斷點恢復執行

雖然多線程可使得任務執行的效率獲得提高,可是因爲在線程切換時一樣會帶來必定的開銷代價,而且多個線程會致使系統資源佔用的增長,因此在進行多線程編程時要注意這些因素。

線程的經常使用方法

編號 方法 說明
1 public void start() 使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
2 public void run() 若是該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;不然,該方法不執行任何操做並返回。
3 public final void setName(String name) 改變線程名稱,使之與參數 name 相同。
4 public final void setPriority(int priority) 更改線程的優先級。
5 public final void setDaemon(boolean on) 將該線程標記爲守護線程或用戶線程。
6 public final void join(long millisec) 等待該線程終止的時間最長爲 millis 毫秒。
7 public void interrupt() 中斷線程。
8 public final boolean isAlive() 測試線程是否處於活動狀態。
9 public static void yield() 暫停當前正在執行的線程對象,並執行其餘線程。
10 public static void sleep(long millisec) 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操做受到系統計時器和調度程序精度和準確性的影響。
11 public static Thread currentThread() 返回對當前正在執行的線程對象的引用。

靜態方法

currentThread()方法

currentThread()方法能夠返回代碼段正在被哪一個線程調用的信息。

public class Run1{ public static void main(String[] args){ System.out.println(Thread.currentThread().getName()); } }

sleep()方法

方法sleep()的做用是在指定的毫秒數內讓當前「正在執行的線程」休眠(暫停執行)。這個「正在執行的線程」是指this.currentThread()返回的線程。

sleep方法有兩個重載版本:

sleep(long millis) //參數爲毫秒 sleep(long millis,int nanoseconds) //第一參數爲毫秒,第二個參數爲納秒

sleep至關於讓線程睡眠,交出CPU,讓CPU去執行其餘的任務。
可是有一點要很是注意,sleep方法不會釋放鎖,也就是說若是當前線程持有對某個對象的鎖,則即便調用sleep方法,其餘線程也沒法訪問這個對象。看下面這個例子就清楚了:

public class Test { private int i = 10; private Object object = new Object(); public static void main(String[] args) throws IOException { Test test = new Test(); MyThread thread1 = test.new MyThread(); MyThread thread2 = test.new MyThread(); thread1.start(); thread2.start(); } class MyThread extends Thread{ @Override public void run() { synchronized (object) { i++; System.out.println("i:"+i); try { System.out.println("線程"+Thread.currentThread().getName()+"進入睡眠狀態"); Thread.currentThread().sleep(10000); } catch (InterruptedException e) { // TODO: handle exception } System.out.println("線程"+Thread.currentThread().getName()+"睡眠結束"); i++; System.out.println("i:"+i); } } } }

輸出結果:

從上面輸出結果能夠看出,當Thread-0進入睡眠狀態以後,Thread-1並無去執行具體的任務。只有當Thread-0執行完以後,此時Thread-0釋放了對象鎖,Thread-1纔開始執行。

注意,若是調用了sleep方法,必須捕獲InterruptedException異常或者將該異常向上層拋出。當線程睡眠時間滿後,不必定會當即獲得執行,由於此時可能CPU正在執行其餘的任務。因此說調用sleep方法至關於讓線程進入阻塞狀態。

yield()方法

調用yield方法會讓當前線程交出CPU權限,讓CPU去執行其餘的線程。它跟sleep方法相似,一樣不會釋放鎖。可是yield不能控制具體的交出CPU的時間,另外,yield方法只能讓擁有相同優先級的線程有獲取CPU執行時間的機會。

注意,調用yield方法並不會讓線程進入阻塞狀態,而是讓線程重回就緒狀態,它只須要等待從新獲取CPU執行時間,這一點是和sleep方法不同的。
代碼:

public class MyThread extends Thread{ @Override public void run() { long beginTime=System.currentTimeMillis(); int count=0; for (int i=0;i<50000000;i++){ count=count+(i+1); //Thread.yield(); } long endTime=System.currentTimeMillis(); System.out.println("用時:"+(endTime-beginTime)+" 毫秒!"); } } public class Run { public static void main(String[] args) { MyThread t= new MyThread(); t.start(); } }

執行結果:

用時:3 毫秒!

若是將 //Thread.yield();的註釋去掉,執行結果以下:

用時:16080 毫秒!

對象方法

start()方法

start()用來啓動一個線程,當調用start方法後,系統纔會開啓一個新的線程來執行用戶定義的子任務,在這個過程當中,會爲相應的線程分配須要的資源。

run()方法

run()方法是不須要用戶來調用的,當經過start方法啓動一個線程以後,當線程得到了CPU執行時間,便進入run方法體去執行具體的任務。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執行的任務。

getId()

getId()的做用是取得線程的惟一標識
代碼:

public class Test { public static void main(String[] args) { Thread t= Thread.currentThread(); System.out.println(t.getName()+" "+t.getId()); } }

輸出:

main 1

isAlive()方法

方法isAlive()的功能是判斷當前線程是否處於活動狀態
代碼:

public class MyThread extends Thread{ @Override public void run() { System.out.println("run="+this.isAlive()); } } public class RunTest { public static void main(String[] args) throws InterruptedException { MyThread myThread=new MyThread(); System.out.println("begin =="+myThread.isAlive()); myThread.start(); System.out.println("end =="+myThread.isAlive()); } }

程序運行結果:

begin ==false run=true end ==false

方法isAlive()的做用是測試線程是否偶處於活動狀態。什麼是活動狀態呢?活動狀態就是線程已經啓動且還沒有終止。線程處於正在運行或準備開始運行的狀態,就認爲線程是「存活」的。
有個須要注意的地方

System.out.println("end =="+myThread.isAlive());

雖然上面的實例中打印的值是true,但此值是不肯定的。打印true值是由於myThread線程還未執行完畢,因此輸出true。若是代碼改爲下面這樣,加了個sleep休眠:

public static void main(String[] args) throws InterruptedException { MyThread myThread=new MyThread(); System.out.println("begin =="+myThread.isAlive()); myThread.start(); Thread.sleep(1000); System.out.println("end =="+myThread.isAlive()); }

則上述代碼運行的結果輸出爲false,由於mythread對象已經在1秒以內執行完畢。

join()方法

在不少狀況下,主線程建立並啓動了線程,若是子線程中藥進行大量耗時運算,主線程每每將早於子線程結束以前結束。這時,若是主線程想等待子線程執行完成以後再結束,好比子線程處理一個數據,主線程要取得這個數據中的值,就要用到join()方法了。方法join()的做用是等待線程對象銷燬。

public class Thread4 extends Thread{ public Thread4(String name) { super(name); } public void run() { for (int i = 0; i < 5; i++) { System.out.println(getName() + " " + i); } } public static void main(String[] args) throws InterruptedException { // 啓動子進程 new Thread4("new thread").start(); for (int i = 0; i < 10; i++) { if (i == 5) { Thread4 th = new Thread4("joined thread"); th.start(); th.join(); } System.out.println(Thread.currentThread().getName() + " " + i); } } }

執行結果:

main 0 main 1 main 2 main 3 main 4 new thread 0 new thread 1 new thread 2 new thread 3 new thread 4 joined thread 0 joined thread 1 joined thread 2 joined thread 3 joined thread 4 main 5 main 6 main 7 main 8 main 9

由上能夠看出main主線程等待joined thread線程先執行完了才結束的。若是把th.join()這行註釋掉,運行結果以下:

main 0 main 1 main 2 main 3 main 4 main 5 main 6 main 7 main 8 main 9 new thread 0 new thread 1 new thread 2 new thread 3 new thread 4 joined thread 0 joined thread 1 joined thread 2 joined thread 3 joined thread 4

getName和setName

用來獲得或者設置線程名稱。

getPriority和setPriority

用來獲取和設置線程優先級。

setDaemon和isDaemon

用來設置線程是否成爲守護線程和判斷線程是不是守護線程。

守護線程和用戶線程的區別在於:守護線程依賴於建立它的線程,而用戶線程則不依賴。舉個簡單的例子:若是在main線程中建立了一個守護線程,當main方法運行完畢以後,守護線程也會隨着消亡。而用戶線程則不會,用戶線程會一直運行直到其運行完畢。在JVM中,像垃圾收集器線程就是守護線程。

在上面已經說到了Thread類中的大部分方法,那麼Thread類中的方法調用到底會引發線程狀態發生怎樣的變化呢?下面一幅圖就是在上面的圖上進行改進而來的:

中止線程

中止線程是在多線程開發時很重要的技術點,掌握此技術能夠對線程的中止進行有效的處理。
中止一個線程可使用Thread.stop()方法,但最好不用它。該方法是不安全的,已被棄用。
在Java中有如下3種方法能夠終止正在運行的線程:

  • 使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止
  • 使用stop方法強行終止線程,可是不推薦使用這個方法,由於stop和suspend及resume同樣,都是做廢過時的方法,使用他們可能產生不可預料的結果。
  • 使用interrupt方法中斷線程,但這個不會終止一個正在運行的線程,還須要加入一個判斷才能夠完成線程的中止。

暫停線程

interrupt()方法

線程的優先級

在操做系統中,線程能夠劃分優先級,優先級較高的線程獲得的CPU資源較多,也就是CPU優先執行優先級較高的線程對象中的任務。
設置線程優先級有助於幫「線程規劃器」肯定在下一次選擇哪個線程來優先執行。
設置線程的優先級使用setPriority()方法,此方法在JDK的源碼以下:

public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } }

在Java中,線程的優先級分爲1~10這10個等級,若是小於1或大於10,則JDK拋出異常throw new IllegalArgumentException()。
JDK中使用3個常量來預置定義優先級的值,代碼以下:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

線程優先級特性:

  • 繼承性
    好比A線程啓動B線程,則B線程的優先級與A是同樣的。
  • 規則性
    高優先級的線程老是大部分先執行完,但不表明高優先級線程所有先執行完。
  • 隨機性
    優先級較高的線程不必定每一次都先執行完。

守護線程

在Java線程中有兩種線程,一種是User Thread(用戶線程),另外一種是Daemon Thread(守護線程)。
Daemon的做用是爲其餘線程的運行提供服務,好比說GC線程。其實User Thread線程和Daemon Thread守護線程本質上來講去沒啥區別的,惟一的區別之處就在虛擬機的離開:若是User Thread所有撤離,那麼Daemon Thread也就沒啥線程好服務的了,因此虛擬機也就退出了。

守護線程並不是虛擬機內部能夠提供,用戶也能夠自行的設定守護線程,方法:public final void setDaemon(boolean on) ;可是有幾點須要注意:

  • thread.setDaemon(true)必須在thread.start()以前設置,不然會跑出一個IllegalThreadStateException異常。你不能把正在運行的常規線程設置爲守護線程。 (備註:這點與守護進程有着明顯的區別,守護進程是建立後,讓進程擺脫原會話的控制+讓進程擺脫原進程組的控制+讓進程擺脫原控制終端的控制;因此說寄託於虛擬機的語言機制跟系統級語言有着本質上面的區別)

  • 在Daemon線程中產生的新線程也是Daemon的。 (這一點又是有着本質的區別了:守護進程fork()出來的子進程再也不是守護進程,儘管它把父進程的進程相關信息複製過去了,可是子進程的進程的父進程不是init進程,所謂的守護進程本質上說就是「父進程掛掉,init收養,而後文件0,1,2都是/dev/null,當前目錄到/」)

  • 不是全部的應用均可以分配給Daemon線程來進行服務,好比讀寫操做或者計算邏輯。由於在Daemon Thread還沒來的及進行操做時,虛擬機可能已經退出了。

同步與死鎖

  1. 同步代碼塊
    在代碼塊上加上"synchronized"關鍵字,則此代碼塊就稱爲同步代碼塊

  2. 同步代碼塊格式

    synchronized(同步對象){ 須要同步的代碼塊; }
  3. 同步方法
    除了代碼塊能夠同步,方法也是能夠同步的
  4. 方法同步格式
    synchronized void 方法名稱(){}
    synchronized後續會單獨來學習。(●'◡'●)

面試題

線程和進程有什麼區別?
答:一個進程是一個獨立(self contained)的運行環境,它能夠被看做一個程序或者一個應用。而線程是在進程中執行的一個任務。線程是進程的子集,一個進程能夠有不少線程,每條線程並行執行不一樣的任務。不一樣的進程使用不一樣的內存空間,而全部的線程共享一片相同的內存空間。別把它和棧內存搞混,每一個線程都擁有單獨的棧內存用來存儲本地數據。

如何在Java中實現線程?
答:
建立線程有兩種方式:
1、繼承 Thread 類,擴展線程。
2、實現 Runnable 接口。

啓動一個線程是調用run()仍是start()方法?
答:啓動一個線程是調用start()方法,使線程所表明的虛擬處理機處於可運行狀態,這意味着它能夠由JVM 調度並執行,這並不意味着線程就會當即運行。run()方法是線程啓動後要進行回調(callback)的方法。

Thread類的sleep()方法和對象的wait()方法均可以讓線程暫停執行,它們有什麼區別?
答:sleep()方法(休眠)是線程類(Thread)的靜態方法,調用此方法會讓當前線程暫停執行指定的時間,將執行機會(CPU)讓給其餘線程,可是對象的鎖依然保持,所以休眠時間結束後會自動恢復(線程回到就緒狀態,請參考第66題中的線程狀態轉換圖)。wait()是Object類的方法,調用對象的wait()方法致使當前線程放棄對象的鎖(線程暫停執行),進入對象的等待池(wait pool),只有調用對象的notify()方法(或notifyAll()方法)時才能喚醒等待池中的線程進入等鎖池(lock pool),若是線程從新得到對象的鎖就能夠進入就緒狀態。

線程的sleep()方法和yield()方法有什麼區別?
答:
① sleep()方法給其餘線程運行機會時不考慮線程的優先級,所以會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;
② 線程執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態;
③ sleep()方法聲明拋出InterruptedException,而yield()方法沒有聲明任何異常;
④ sleep()方法比yield()方法(跟操做系統CPU調度相關)具備更好的可移植性。

請說出與線程同步以及線程調度相關的方法。
答:

  • wait():使一個線程處於等待(阻塞)狀態,而且釋放所持有的對象的鎖;
  • sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理InterruptedException異常;
  • notify():喚醒一個處於等待狀態的線程,固然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM肯定喚醒哪一個線程,並且與優先級無關;
  • notityAll():喚醒全部處於等待狀態的線程,該方法並非將對象的鎖給全部線程,而是讓它們競爭,只有得到鎖的線程才能進入就緒狀態;

總結

以上就是多線程的一些基礎概念,可能總結的不夠仔細,多多包涵。後續會針對一些比較重要的知識點單獨列出來總結。學好多線程是拿高薪的基礎,小夥伴一塊兒加油吧!

參考

該文爲本人學習的筆記,方便之後本身跳槽前複習。參考網上各大帖子,取其精華整合本身的理解而成。還有,關注我我的主頁的公衆號,裏面電子書資源有《Java多線程編程核心技術》以及《JAVA併發編程實踐》高清版,須要的小夥伴本身取。

《Java多線程編程核心技術》
《JAVA併發編程實踐》
Java併發編程:Thread類的使用
關於Java併發編程的總結和思考
JAVA多線程實現的三種方式

整理的思惟導圖

我的整理的多線程基礎的思惟導圖,導出的圖片沒法查看備註的一些信息,因此須要源文件的童鞋能夠關注我我的主頁上的公衆號,回覆多線程基礎便可獲取源文件。


一直以爲本身寫的不是技術,而是情懷,一篇篇文章是本身這一路走來的痕跡。靠專業技能的成功是最具可複製性的,但願個人這條路能讓你少走彎路,但願我能幫你抹去知識的蒙塵,但願我能幫你理清知識的脈絡,但願將來技術之巔上有你也有我。

 

 

 

 

 

原文連接:http://www.jianshu.com/p/4e2343cf747b

相關文章
相關標籤/搜索