建立線程的方式有不少種,下面咱們就最基本的兩種方式進行說明。主要先介紹使用方式,再從源碼角度進行解析。java
這兩種方式是最基本的建立線程的方式,其實核心也就是Thread類,後面分析源碼會講到,下面先介紹使用方式。編程
1,建立線程步驟設計模式
代碼以下:多線程
package com.yefengyu.thread; //1,建立一個子類繼承於Thread類 public class SubThread extends Thread { //2,子類重寫Thread類的run方法,方法內實現子線程要完成的功能 @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Thread ...." + i); } } }
package com.yefengyu.thread; public class TestThread { public static void main(String[] args) { //3,建立一個子類的對象。 SubThread subThread = new SubThread(); //4,調用線程的start()的方法。該方法有兩個做用:啓動此線程;調用響應的run方法。不能顯示調用run方法,由於這樣不能開啓線程 subThread.start(); for (int i = 0; i < 5; i++) { System.out.println("main ...." + i); } } }
每次執行結果都不相同,這是由於多個線程都在獲取cpu的執行權,cpu執行到誰,就執行誰。可是要明確一點,在某個時刻,單個cpu只能執行一個線程,cpu進行着快速切換,已達到看上去是並行執行的效果,這就是線程的一個特色:隨機性,哪一個線程搶到cpu資源,就執行該線程,至於執行多長時間,是cpu說了算。 ide
main ....0 Thread ....0 Thread ....1 Thread ....2 main ....1 main ....2 main ....3 main ....4 Thread ....3 Thread ....4
2,設置與獲取線程名稱函數
因爲默認的線程名稱沒有可讀性,所以設置一個線程名稱仍是比較重要的,Thread類有個構造方法:源碼分析
public Thread(String name) { init(null, null, name, 0); }
所以子類增長線程名稱則比較簡單:學習
package com.yefengyu.thread; //1,建立一個子類繼承於Thread類 public class SubThread extends Thread { //經過構造函數設置線程名稱,固然使用set方法也能夠 public SubThread(String name) { super(name); } //2,子類重寫Thread類的run方法,方法內實現子線程要完成的功能 @Override public void run() { for (int i = 0; i < 5; i++) { //下面兩種方法都會獲取線程名稱 System.out.println(this.getName()+" ... " + i); System.out.println(Thread.currentThread().getName()+" *** " + i); } } }
package com.yefengyu.thread; public class TestThread { public static void main(String[] args) { //(3)建立一個子類的對象。 SubThread subThread1 = new SubThread("my-thread11111"); SubThread subThread2 = new SubThread("my-thread22222"); //(4)調用線程的start()的方法。該方法有兩個做用:啓動此線程;調用響應的run方法。不能顯示調用run方法,由於這樣不能開啓線程 subThread1.start(); subThread2.start(); for (int i = 0; i < 5; i++) { System.out.println("main ...." + i); } } }
結果:this
main ....0 my-thread22222 ... 0 my-thread11111 ... 0 my-thread22222 *** 0 my-thread22222 ... 1 my-thread22222 *** 1 main ....1 my-thread22222 ... 2 my-thread11111 *** 0 my-thread22222 *** 2 main ....2 main ....3 main ....4 my-thread22222 ... 3 my-thread11111 ... 1 my-thread11111 *** 1 my-thread11111 ... 2 my-thread22222 *** 3 my-thread22222 ... 4 my-thread22222 *** 4 my-thread11111 *** 2 my-thread11111 ... 3 my-thread11111 *** 3 my-thread11111 ... 4 my-thread11111 *** 4
3,實戰:汽車票買票程序spa
假如車站有3張票,三個窗口,多線程如何賣票?
package com.yefengyu.thread; public class SubThread extends Thread { //票的總數 private int ticket = 3; public SubThread(String name) { super(name); } @Override public void run() { while (true) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "賣票 " + ticket--); } } } }
package com.yefengyu.thread; public class TestThread { public static void main(String[] args) { //三個線程模擬三個窗口同時賣票 SubThread subThread1 = new SubThread("my-thread11111"); SubThread subThread2 = new SubThread("my-thread22222"); SubThread subThread3 = new SubThread("my-thread33333"); subThread1.start(); subThread2.start(); subThread3.start(); } }
結果和咱們想的大不相同,居然每一個線程都賣了3張票,一共賣了9張票,這是不能夠忍受的。
my-thread11111賣票 3 my-thread11111賣票 2 my-thread11111賣票 1 my-thread33333賣票 3 my-thread22222賣票 3 my-thread33333賣票 2 my-thread22222賣票 2 my-thread33333賣票 1 my-thread22222賣票 1
修改1:使用靜態變量:
//票的總數 private static int ticket = 3;
定義靜態能夠解決賣出多餘票的狀況,可是這種變量通常不定義靜態的,由於靜態屬性生命週期太長。
修改2:new一個SubThread實例,屢次啓動
package com.yefengyu.thread; public class TestThread { public static void main(String[] args) { //一個線程對象屢次啓動 SubThread subThread1 = new SubThread("my-thread11111"); subThread1.start(); subThread1.start(); subThread1.start(); } }
出現異常:
my-thread11111賣票 3Exception in thread "main" my-thread11111賣票 2 my-thread11111賣票 1 java.lang.IllegalThreadStateException at java.lang.Thread.start(Thread.java:708) at com.yefengyu.thread.TestThread.main(TestThread.java:8)
源碼這樣說:線程不是NEW狀態是不能夠調用start方法的,調用會報異常,也就是一個線程啓動以後不能再啓動。關於線程狀態後面博文會講到。
/** * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException();
如何解決賣票程序中的問題呢?使用Runnable接口。
1,建立線程步驟
2,代碼演示
package com.yefengyu.thread; public class SubThread implements Runnable { //票的總數 private int ticket = 3; @Override public void run() { while (true) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "賣票 " + ticket--); } } } }
package com.yefengyu.thread; public class TestThread { public static void main(String[] args) { //建立該類的對象 SubThread subThread = new SubThread(); //在建立 Thread 時做爲一個參數來傳遞並啓動 new Thread(subThread, "線程1").start(); new Thread(subThread, "線程2").start(); new Thread(subThread, "線程3").start(); } }
運行結果以下,是咱們想要的結果。
線程1賣票 3 線程3賣票 2 線程2賣票 1
稍微研究一下,第一種繼承Thread類的方式,new了3次SubThread實例,那麼實例的變量ticket也有三個,線程分別擁有各自的ticket。而實現Runnable接口的方式,數據只存在與SubThread這個實例對象中,代碼中只需new一次,所以只有一份ticket數據,而將持有這份數據的對象經過構造方法傳入到多個線程中的時候,線程對象只是執行的載體,真實數據只有一份,所以Runnable接口的實現方式適合多個相同的程序代碼的線程去處理同一個資源。
3,本節小總結
1,理論分析
多線程是java開發中必不可少的一項技術點,下面主要研究Thread類,經過分析該類瞭解多線程執行的過程,爲之後的線程池等高級技術打下堅實基礎。
當咱們使用多線程進行開發的時候,最開始學習的例子就是使用Thread類。使用步驟以下,和上面演示的對比,簡化了步驟:
編寫一個類,繼承Thread
重寫run方法
經過調用start方法啓動線程。
後來又有一種方法,實現Runnable接口,主要步驟以下:
編寫一個類,實現Runnable 接口,重寫run方法
將該類的對象傳入Thread類中
經過調用start方法啓動線程。
經過上面,咱們來分析一下,線程執行離不開Thread類,最後都要使用start方法啓動線程。咱們能夠想到start方法是一個入口方法,它能夠作不少事。假如start方法作了以下的事情:
do x
do y
do run
do z
咱們不關心x、y、z具體是什麼,只要明白start方法是一個入口方法,它作了不少事情,可是在某一步,它調用了 run 方法。而run方法是咱們必須實現的,也就是咱們本身實現的邏輯在start裏面被執行了。接着咱們考慮下 run 方法,怎麼樣才能本身定義run方法,而後在run方法裏面寫本身的邏輯?
一種方法是,在Thread類裏面,咱們定義一個抽象方法 run,這個時候,必須有子類來實現。這是模板設計模式思想。
另外一種方法是提供一個接口,而且接口中有個方法 run,Thread類持有這個接口(經過屬性持有,再經過構造器傳入),而且Thread類也有個run方法(爲啥也要有個run方法後面會提到),該run方法調用接口的run方法。此時只要編寫一個類實現接口,重寫run方法,並傳入Thread類,那麼Thread類在執行start方法的時候,會調用自身的run方法,該run方法又會調用接口實現的run方法,這是策略設計模式思想。
以上兩種模式就是實現Thread類和實現Runnable接口的實現原理。須要注意的是,Thread類自己也實現了Runnable接口,那麼Thread類自己擁有run方法則水到渠成。
2,源碼分析
Runnable的源碼很簡單:
@FunctionalInterface public interface Runnable { public abstract void run(); }
Thread類的屬性很是多,咱們暫時無論,注意有一個屬性,它是對Runnable接口的引用。
private Runnable target;
有了屬性,咱們須要看如何傳入這個屬性值:
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); }
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
init方法內容不少,可是關於target變量,只有一句:
this.target = target;
所以能夠判斷,上面兩個構造器,第一個沒有給Runnable賦值,值爲null,第二個經過參數進行賦值。
咱們再看下Thread類:
public class Thread implements Runnable
Thread類實現了Runnable接口,所以必須實現run方法:
@Override public void run() { if (target != null) { target.run(); } }
對於這個run方法,若是是使用繼承Thread類重寫run方法;那麼這裏面的內容將會被覆蓋,若是是實現Runnable接口重寫run方法,那麼此處就會調用接口實現的run方法。這就讓兩種實現多線程的方式得以共存。
這個run方法什麼時候調用?在start方法中:
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } private native void start0();
注意start方法中的start0方法和最後一行start0方法。該start0方法是native方法,實質是調用run方法,此處暫時不作詳解。
Thread類使用模板設計模式,模板方法是start,start方法裏面的start0方法才能真正啓動線程、調用了Thread類的run方法。
3,Runnable接口的好處: