本文經過幾個問題入手,主要經過案例講解Thread類的使用,以及注意事項,最後站在源碼的角度分析問題。java
多線程不必定快!多線程
多線程只是極大限度的利用CPU的空閒時間來處理其餘的任務,這樣纔會縮短多個任務的執行時間。若是在沒有CPU空閒的狀況下,多線程之間的上下文切換(保存線程的運行環境,還原線程的運行環境)會產生開銷,執行時間會慢於單線程。異步
單任務操做系統:同步執行,排隊。只有當前任務執行完了才能夠執行下一個任務。例如cmd中執行完一條指令以後才能夠執行另外一條指令ide
多任務操做系統:異步執行,經過時間片的快速切換實現多個任務響應執行。源碼分析
(1)繼承Thread類,重寫run( )方法性能
package com.feng.example; public class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub super.run(); System.out.println("繼承Thread類"); } }
啓動線程:測試
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub MyThread myThread = new MyThread(); myThread.start(); } }
(2)建立一個Runnable接口的實現類A,將A的實例對象做爲Thread類的參數傳入this
package com.feng.example; public class MyRunnable implements Runnable { @Override public void run() { // TODO Auto-generated method stub System.out.println("經過實現Runnable接口重寫run"); } }
啓動線程:spa
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable myRunnable = new MyRunnable(); Thread myThread = new Thread(myRunnable); myThread.start(); } }
因爲在Thread類也是實現Runnable接口的,所以Thread的對象也能夠做爲第二種形式的參數操作系統
public class Thread implements Runnable {} //這裏只是貼出源碼中Thread類的定義
將第一種方式建立的MyThread類對象做爲第二種方式的輸入參數
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //建立MyThread類對象 Runnable myRunnable = new MyThread(); Thread myThread = new Thread(myRunnable); myThread.start(); } }
兩種方式的區別:兩種方式沒有本質的區別,通常都會使用第二種形式,主要解決java中類不能多繼承的缺陷。
好比Cat類繼承Animal,同時又想將Cat定義爲線程類,由於Cat不能同時繼承Animal,Thread類,可是Cat能夠繼承Animal,實現Runnable接口
回顧一下操做系統課中線程的建立過程:線程的建立包括兩部分
(1)建立線程,至關於MyThread myThread = new MyThread( );
(2)將線程放到就緒隊列中,若是僅僅只是建立了線程卻沒有將線程放到就緒隊列中,線程調度器是無法找到線程的。所以myThread.start( );將線程放到就緒隊列中。等待分配cpu時間片,當獲取了cpu以後就能夠執行線程的run( )方法
經過start( )方法:是新開闢了一個線程,run( )方法是由線程規劃器來調用的,是異步運行
直接調用run( )方法:調用者是Thread子類的一個對象,屬於同步執行,沒有開闢新的線程。
package com.feng.example; public class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub super.run(); System.out.println("線程爲:"+Thread.currentThread().getName()); } }
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //建立MyThread類對象 MyThread myThread = new MyThread(); myThread.start(); myThread.run(); //同步調用run方法 } }
程序運行結果:
首先查看Thread類源碼中是如何重寫Runnable接口中的run( )方法的。
public void run() { if (target != null) { target.run(); } }
接下來看一下這個target又是什麼?
/* What will be run. */ private Runnable target;
由源碼能夠看出,在Thread類的內部,存在一個類型爲Runnable的成員變量,此成員變量即是接收第二種建立線程類的方法中的輸入參數的。再看Thread類的run方法,只是判斷有此target存不存在,若是存在便執行target的run( )方法。
若是不存在怎麼辦?
若是不存在則是使用繼承的方式來實現的線程類,那麼run( )方法已經在Thread子類中進行了重寫,覆蓋了父類的run( )方法
下面經過一個題目來消化這個知識點:
Runnable接口的實現類以下:
package com.feng.example; public class MyRunnable implements Runnable { @Override public void run() { // TODO Auto-generated method stub System.out.println("經過Runnable接口實現線程類"); } }
Thread的子類實現以下:
package com.feng.example; public class MyThread extends Thread { public MyThread(Runnable target) { super(target); } @Override public void run() { // TODO Auto-generated method stub System.out.println("經過繼承實現線程類"); } }
測試代碼以下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //建立Runnable接口實現類 Runnable myRunnable = new MyRunnable(); //建立MyThread類對象 MyThread myThread = new MyThread(myRunnable); //到底輸出的是誰的run方法???? myThread.start(); } }
分析: 首先start的是MyThread線程,由於會執行MyThread類的run( )方法。雖然傳入了Runnable類型的參數,可是在MyThread類中的run( )方法中並無super.run( );所以程序只輸出MyThread類中的run( )方法中的輸出。
輸出結果如圖:
修改MyThread類中的run( )方法:
package com.feng.example; public class MyThread extends Thread { public MyThread(Runnable target) { super(target); } @Override public void run() { // TODO Auto-generated method stub super.run(); System.out.println("經過繼承實現線程類"); } }
分析:
首先start的是MyThread線程,由於會執行MyThread類的run( )方法。由於在構造MyThread時傳入了Runnable類型的參數,所以在執行MyThread類中的run( )方法時,先執行super.run( ); 執行Thread類的run( ); 根據源代碼先檢查target存不存,這裏target就是存入的myRunnable, 因此執行myRunnable裏面的run( );執行完super.run( )方法後,在輸出後面的語句。
輸入結果以下圖:
在有些地方可能會以匿名類的形式來進行考察:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { new Thread(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub System.out.println("Runnable..."); } }){ public void run() { //super.run(); System.out.println("Thread..."); } }.start(); } }
看到此代碼不要驚慌,其實與我最初定義的三個類文件的考察方式是同樣的,只不過這裏使用了匿名類,若是忘記了回去翻看匿名類的知識,此題注意註釋的那一句super.run( ); 看一下注釋不註釋有什麼區別。
只要理解Thread源碼中的run( )方法,這個題目就顯得很簡單了。
1、在研究join的用法以前,先明確兩件事情。
1.join方法定義在Thread類中,則調用者必須是一個線程,
例如:
Thread t = new CustomThread();//這裏通常是自定義的線程類
t.start();//線程起動
t.join();//此處會拋出InterruptedException異常
2.上面的兩行代碼也是在一個線程裏面執行的
以上出現了兩個線程,一個是咱們自定義的線程類,咱們實現了run方法,作一些咱們須要的工做;另一個線程,生成咱們自定義線程類的對象,而後執行
customThread.start();
customThread.join();
在這種狀況下,兩個線程的關係是一個線程由另一個線程生成並起動,因此咱們暫且認爲第一個線程叫作「子線程」,另一個線程叫作「主線程」。
2、爲何要用join()方法
主線程生成並起動了子線程,而子線程裏要進行大量的耗時的運算(這裏能夠借鑑下線程的做用),當主線程處理完其餘的事務後,須要用到子線程的處理結果,這個時候就要用到join();方法了。
3、join方法的做用
在網上看到有人說「將兩個線程合併」。這樣解釋我以爲理解起來還更麻煩。不如就借鑑下API裏的說法:
「等待該線程終止。」
解釋一下,是主線程(我在「一」裏已經命名過了)等待子線程的終止。也就是在子線程調用了join()方法後面的代碼,只有等到子線程結束了才能執行。(Waits for this thread to die.)
進入源碼
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;
}
}
}
從實現角度來講:
sychronied 關鍵字是JVM層實現的,由其提供內置的鎖。而lock方式必須被顯示的建立
從代碼角度來講
lock方式,代碼缺少優雅性。sychronized代碼更加簡潔,代碼量少。
從使用角度來講
lock方式更加靈活,可是隻有在解決特殊問題的時候纔會使用。
用sychronized關鍵字不能嘗試着獲取鎖且最終獲取鎖會失敗,或者嘗試着獲取鎖一段時間,而後放棄它,要實現這些必須使用lock方式。
使用sychronized關鍵字時,若是某些事務失敗了,那麼就會拋出異常,由JVM自動釋放線程資源,可是你沒法也沒有機會去作任何的清理工做,以維護系統使其處於良好的狀態。使用顯示的Lock方式,你就可使用finally子句將系統維護在正確的狀態了。
顯示的Lock對象在加鎖和釋放鎖方面,相對於內建的sychronized鎖來講,還賦予了你耕細粒度的控制力。
從性能角度來講:
在資源競爭不是很激烈的狀況下,Synchronized的性能要優於ReetrantLock,可是在資源競爭很激烈的狀況下,Synchronized的性能會降低幾十倍,可是ReetrantLock的性能能維持常態