下面是不添加線程的程序代碼。java
package concurrent.chapter01; import java.util.concurrent.TimeUnit; public class TryConcurrency { public static void main(String[] args) { browseNews(); enjoyMusic(); } private static void browseNews() { while(true) { System.out.println("Uh-huh,the good news."); sleep(1); } } private static void enjoyMusic() { while(true) { System.out.println("Uh-huh,the nice music"); sleep(1); } } private static void sleep(int i) { try { TimeUnit.SECONDS.sleep(i); }catch (Exception e) { } } }
運行結果以下:算法
程序永遠不會執行第二個方法。所以咱們須要使用線程。sql
這裏經過匿名內部類的方式建立線程,而且重寫其中的run方法,使程序交互運行。數據庫
package concurrent.chapter01; import java.util.concurrent.TimeUnit; public class TryConcurrency { public static void main(String[] args) { new Thread() { @Override public void run() { enjoyMusic(); } }.start(); browseNews(); } private static void browseNews() { while(true) { System.out.println("Uh-huh,the good news."); sleep(1); } } private static void enjoyMusic() { while(true) { System.out.println("Uh-huh,the nice music"); sleep(1); } } private static void sleep(int i) { try { TimeUnit.SECONDS.sleep(i); }catch (Exception e) { } } }
運行結果以下:編程
注意:設計模式
一、建立一個線程,須要重寫Thread中的run方法,Override的註解是重寫的標識,而後將enjoyMusic交給他執行。安全
二、啓動新的線程須要重寫Thread的start方法,才表明派生了一個新的線程,不然Thread和其餘普通的Java對象並沒有區別,start放法是一個當即返回方法,並不會讓程序陷入阻塞。網絡
若是使用Lambda表達式改造上面的代碼,那麼代碼會變得更簡潔。數據結構
public static void main(String[] args) { new Thread(TryConcurrency::enjoyMusic).start(); browseNews(); }
當咱們用關鍵字new建立一個Thread對象時,此時他並不處於執行狀態,由於沒用start啓動該線程,那麼線程的狀態爲NEW狀態,準確的說,它只是Thread對象的狀態,由於在沒用start以前,該線程根本不存在,與你用new建立一個普通的Java對象沒什麼區別。併發
線程對象進入RUNNABLE狀態必須調用start方法,那麼此時纔是真正地在JVM中建立了一個線程,線程一經啓動就能夠當即執行嗎?答案是否認的,線程的運行與否和進程同樣都要聽令於CPU的調度,那麼咱們把這個中間狀態成爲可執行狀態,也就是說它具有執行的資格,可是並無真正地執行起來,而是等待CPU的調度。
一旦CPU經過輪詢或者其餘方式從任務可執行隊列中選中了線程,那麼此時它才能真正的執行本身的邏輯代碼,須要說明一點是一個正在RUNNING狀態的線程事實上也是RUNNABLE的,可是反過來則不成立。
在該狀態中,線程的狀態能夠發生以下的狀態轉換。
BLOCKED爲線程阻塞時的狀態,它能進入如下幾個狀態:
TERMINATED是一個線程的最終狀態,在該狀態中,線程將不會切換到其餘任何狀態,線程進入Terminated狀態意味着整個線程的生命週期結束了,下列狀況將會使線程進入Terminated狀態。
首先:Thread start源碼以下:
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } private native void start0();
start方法的源碼足夠簡單,其實最核心的部分是start0這個本地方法,也就是JNI方法;
也就是說在start方法中會調用start0方法,那麼重寫的那個run方法什麼時候被調用了呢?
實際上在開始執行這個線程的時候,JVM將會調用該線程的run方法,換言之,run方法是被JNI方法start0調用的,仔細閱讀start的源碼將會總結出以下幾個知識要點。
如執行如下代碼:
import java.util.concurrent.TimeUnit; public class A { public static void main(String[] args) { Thread thread = new Thread() { @Override public void run() { try { TimeUnit.SECONDS.sleep(10); }catch (Exception e) { e.printStackTrace(); } } }; thread.start(); thread.start(); } }
此時程序就會拋出
當咱們改下代碼,也就是生命週期結束後,再從新調用時。
import java.util.concurrent.TimeUnit; public class A { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread() { @Override public void run() { try { TimeUnit.SECONDS.sleep(1); }catch (Exception e) { e.printStackTrace(); } } }; thread.start(); TimeUnit.SECONDS.sleep(5); thread.start(); } }
咱們會發現程序一樣會拋出illegalThread異常。
注意:程序雖然一樣會拋出異常,可是這倆個異常是有本質區別的。
經過以上分析咱們不難看出,線程真正的執行邏輯是在run方法中,一般咱們會把run方法成爲線程的執行單元。
若是咱們沒有重寫run,那run就是個空方法。
Thread的run和start是一個比較經典的模板設計模式,父類編寫算法結構代碼,子類實現邏輯細節,下面是一個簡單的模板設計模式。
package concurrent.chapter01; public class TemplateMethod { public final void print(String message) { System.out.println("###"); wrapPrint(message); System.out.println("###"); } protected void wrapPrint(String message) { } public static void main(String[] args) { TemplateMethod t1 = new TemplateMethod() { @Override protected void wrapPrint(String message) { System.out.println("*"+message+"*"); } }; t1.print("Hello Thread"); TemplateMethod t2 = new TemplateMethod() { @Override protected void wrapPrint(String message) { System.out.println("+"+message+"+"); } }; t2.print("Hello Thread"); } }
運行結果以下:
假設共有4臺出號機,這就意味着有4個線程在工做,下面咱們用程序模擬一下叫號的過程,約定當天最多受理50筆業務,也就是說號碼最多能夠出到50
代碼以下:
package concurrent.chapter01; public class TicketWindow extends Thread{ private final String name; private static final int MAX = 50; private int index = 1; public TicketWindow(String name) { this.name=name; } @Override public void run() { while(index<=MAX) { System.out.println("櫃檯:"+name+" 當前號碼是:"+(index++)); } } public static void main(String[] args) { TicketWindow t1 = new TicketWindow("一號初號機"); t1.start(); TicketWindow t2 = new TicketWindow("二號初號機"); t2.start(); TicketWindow t3 = new TicketWindow("三號初號機"); t3.start(); TicketWindow t4 = new TicketWindow("四號初號機"); t4.start(); } }
運行結果以下:
顯然這不是咱們想看到的。如何改進呢?
這裏我將index設置爲staic變量
貌似有了改善。可是會出現線程安全問題。
因此Java提供了一個接口:Runnable專門用於解決該問題,將線程和業務邏輯的運行完全分離開。
Runnalbe接口很是簡單,只是定義了一個無參數無返回值的run方法,具體代碼以下:
public interface Runnable{ void run(); }
在不少書中,都會說,建立線程有倆種方式,第一種是構造一個Thread,第二種是實現Runnable接口,這種說法是錯誤的,最起碼是不嚴謹的,在JDK中,表明線程的就只有Thread這個類,咱們在前面分析過,線程的執行單元就是run方法,你能夠經過繼承Thread而後重寫run方法實現本身的業務邏輯,也能夠實現Runnable接口實現本身的業務邏輯,代碼以下:
@override public void run(){ if(target!=null){ target.run(); } }
上面的代碼段是Thread run方法的源碼,咱們從中能夠去理解,建立線程只有一種方式,那就是構造Thread類,而實現線程的執行單元則有倆種方式,第一種是重寫Thread的run方法,第二種是實現Runnable接口的run方法,並將Runnable實例用做構造Thread的參數。
其實不管是Runnable的run方法仍是,Thread自己的run方法都說想將線程的控制自己和業務邏輯的運行分離開,達到職責分明,功能單一的原則,這一點與GoF設計模式中的策略設計模式很相近。
以JDBC來舉例子:
package concurrent.chapter01; import java.sql.ResultSet; public interface RowHandler <T>{ T handle(ResultSet set); }
rowhandler接口只負責對從數據庫中查詢出來的結果集進行操做,至於最終返回成什麼樣的數據結構,須要本身去實現,相似於Runnable接口。
package concurrent.chapter01; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class RecordQuery { private final Connection connection; public RecordQuery(Connection connection) { this.connection = connection; } public<T> T query(RowHandler<T> handler,String sql,Object... params) throws SQLException{ try(PreparedStatement stmt = connection.prepareStatement(sql)){ int index = 1; for(Object param:params) { stmt.setObject(index++,param); } ResultSet resultSet = stmt.executeQuery(); return handler.handle(resultSet); } } }
上面的代碼的好處就是能夠用Query方法應對任何數據庫的查詢,返回結果的不一樣只會由於你傳入RowHandler的不一樣而不一樣,一樣RecodeQuery只負責數據的獲取,而RowHanlder則只負責數據的加工,職責分明,每一個類均功能單一。
重寫Thread類的run方法和實現Runnable接口的run方法是不能共享的,也就是說A線程不能把B線程的run方法看成本身的執行單元,而使用Runnable接口則很容易就能實現這一點即便用同一個Runnable的實例構造不一樣的實例。
若是不明白的話,看下面的代碼。
package concurrent.chapter01; public class TicketWindowRunnable implements Runnable{ private int index = -1; private final static int MAX = 50; @Override public void run() { while(index<=MAX) { System.out.println(Thread.currentThread()+" 的號碼是:"+(index++)); try { Thread.sleep(100); }catch (Exception e) { e.printStackTrace(); } } } public static void main(String[] args) { final TicketWindowRunnable task = new TicketWindowRunnable(); Thread WindowThread1 = new Thread(task,"一號窗口"); Thread WindowThread2 = new Thread(task,"二號窗口"); Thread WindowThread3 = new Thread(task,"三號窗口"); Thread WindowThread4 = new Thread(task,"四號窗口"); WindowThread1.start(); WindowThread2.start(); WindowThread3.start(); WindowThread4.start(); } }
運行結果以下:
驚不驚喜?
上面並無對index進行static進行修飾,可是和上面被static修飾的是一個效果。緣由是咱們每次操做的都是同一個對象即task。