到目前爲止,你學到的都是順序編程,順序編程的概念就是某一時刻只有一個任務在執行,順序編程當然可以解決不少問題,可是對於某種任務,若是可以併發的執行程序中重要的部分就顯得尤其重要,同時也能夠極大提升程序運行效率,享受併發爲你帶來的便利。可是,熟練掌握併發編程理論和技術,對於只會CRUD的你來講是一種和你剛學面向對象同樣的一種飛躍。html
正如你所看到的,當並行的任務彼此干涉時,實際的併發問題就會接踵而至。並且併發問題不是很難復現,在你實際的測試過程當中每每會忽略它們,由於故障是偶爾發生的,這也是咱們研究它們的必要條件:若是你對併發問題置之不理,那麼你最終會承受它給你帶來的損害。java
速度問題聽起來很簡單,若是你想讓一個程序運行的更快一些,那麼能夠將其切成多個分片,在單獨的處理器上運行各自的分片:前提是這些任務彼此之間沒有聯繫。編程
注意:速度的提升是以多核處理器而不是芯片的形式出現的。多線程
若是你有一臺多處理器的機器,那麼你就能夠在這些處理器之間分佈多個任務,從而極大的提升吞吐量。**可是,併發一般是提升在單處理器上的程序的性能。**在單處理上的性能開銷要比多處理器上的性能開銷大不少,由於這其中增長了線程切換
(從一個線程切換到另一個線程)的重要依據。表面上看,將程序的全部部分看成單個的任務運行好像是開銷更小一點,節省了線程切換的時間。併發
在單CPU機器上使用多任務的程序在任意時刻仍舊只在執行一項工做,你肉眼觀察到控制檯的輸出好像是這些線程在同時工做,這不過是CPU的障眼法罷了,CPU爲每一個任務都提供了不固定的時間切片。Java 的線程機制是搶佔式的,也就是說,你必須編寫某種讓步語句纔會讓線程進行切換,切換給其餘線程。異步
併發編程使咱們能夠將程序劃分紅多個分離的,獨立運行的任務。經過使用多線程機制,這些獨立任務中的每一項任務都由執行線程
來驅動。一個線程就是進程中的一個單一的順序控制流。所以,單個進程能夠擁有多個併發執行的任務,可是你的程序看起來每一個任務都有本身的CPU同樣。其底層是切分CPU時間,一般你不須要考慮它。ide
線程能夠驅動任務,所以你須要一種描述任務的方式,這能夠由 Runnable
接口來提供,要想定義任務,只須要實現 Runnable 接口,並在run
方法中實現你的邏輯便可。性能
public class TestThread implements Runnable{ public static int i = 0; @Override public void run() { System.out.println("start thread..." + i); i++; System.out.println("end thread ..." + i); } public static void main(String[] args) { for(int i = 0;i < 5;i++){ TestThread testThread = new TestThread(); testThread.run(); } } }
任務 run 方法會有某種形式的循環,使得任務一直運行下去直到再也不須要,因此要設定 run 方法的跳出條件(有一種選擇是從 run 中直接返回,下面會說到。)測試
在 run 中使用靜態方法 Thread.yield()
可使用線程調度,它的意思是建議線程機制進行切換:你已經執行完重要的部分了,剩下的交給其餘線程跑一跑吧。注意是建議執行,而不是強制執行。在下面添加 Thread.yield() 你會看到有意思的輸出this
public void run() { System.out.println("start thread..." + i); i++; Thread.yield(); System.out.println("end thread ..." + i); }
將 Runnable 轉變工做方式的傳統方式是使用 Thread 類託管他,下面展現了使用 Thread 類來實現一個線程。
public static void main(String[] args) { for(int i = 0;i < 5;i++){ Thread t = new Thread(new TestThread()); t.start(); } System.out.println("Waiting thread ..."); }
Thread 構造器只須要一個 Runnable 對象,調用 Thread 對象的 start() 方法爲該線程執行必須的初始化操做,而後調用 Runnable 的 run 方法,以便在這個線程中啓動任務。能夠看到,在 run 方法尚未結束前,run 就被返回了。也就是說,程序不會等到 run 方法執行完畢就會執行下面的指令。
在 run 方法中打印出每一個線程的名字,就更能看到不一樣的線程的切換和調度
@Override public void run() { System.out.println(Thread.currentThread() + "start thread..." + i); i++; System.out.println(Thread.currentThread() + "end thread ..." + i); }
這種線程切換和調度是交由 線程調度器
來自動控制的,若是你的機器上有多個處理器,線程調度器會在這些處理器之間默默的分發線程。每一次的運行結果都不盡相同,由於線程調度機制是未肯定的。
JDK1.5 的java.util.concurrent 包中的執行器 Executor 將爲你管理 Thread 對象,從而簡化了併發編程。Executor 在客戶端和任務之間提供了一個間接層;與客戶端直接執行任務不一樣,這個中介對象將執行任務。Executor 容許你管理異步任務的執行,而無須顯示地管理線程的生命週期。
public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); for(int i = 0;i < 5;i++){ service.execute(new TestThread()); } service.shutdown(); }
咱們使用 Executor 來替代上述顯示建立 Thread 對象。CachedThreadPool
爲每一個任務都建立一個線程。注意:ExecutorService 對象是使用靜態的 Executors
建立的,這個方法能夠肯定 Executor 類型。對 shutDown
的調用能夠防止新任務提交給 ExecutorService ,這個線程在 Executor 中全部任務完成後退出。
FixedThreadPool 使你可使用有限的線程集來啓動多線程
public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(5); for(int i = 0;i < 5;i++){ service.execute(new TestThread()); } service.shutdown(); }
有了 FixedThreadPool 使你能夠一次性的預先執行高昂的線程分配,所以也就能夠限制線程的數量。這能夠節省時間,由於你沒必要爲每一個任務都固定的付出建立線程的開銷。
SingleThreadExecutor 就是線程數量爲 1 的 FixedThreadPool,若是向 SingleThreadPool 一次性提交了多個任務,那麼這些任務將會排隊,每一個任務都會在下一個任務開始前結束,全部的任務都將使用相同的線程。SingleThreadPool 會序列化全部提交給他的任務,並會維護它本身(隱藏)的懸掛隊列。
public static void main(String[] args) { ExecutorService service = Executors.newSingleThreadExecutor(); for(int i = 0;i < 5;i++){ service.execute(new TestThread()); } service.shutdown(); }
從輸出的結果就能夠看到,任務都是挨着執行的。我爲任務分配了五個線程,可是這五個線程不像是咱們以前看到的有換進換出的效果,它每次都會先執行完本身的那個線程,而後餘下的線程繼續「走完」這條線程的執行路徑。你能夠用 SingleThreadExecutor 來確保任意時刻都只有惟一一個任務在運行。
Runnable 是執行工做的獨立任務,但它不返回任何值。若是你但願任務在完成時可以返回一個值 ,這個時候你就須要考慮使用 Callable
接口,它是 JDK1.5 以後引入的,經過調用它的 submit
方法,能夠把它的返回值放在一個 Future 對象中,而後根據相應的 get() 方法取得提交以後的返回值。
public class TaskWithResult implements Callable<String> { private int id; public TaskWithResult(int id){ this.id = id; } @Override public String call() throws Exception { return "result of TaskWithResult " + id; } } public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executors = Executors.newCachedThreadPool(); ArrayList<Future<String>> future = new ArrayList<>(); for(int i = 0;i < 10;i++){ // 返回的是調用 call 方法的結果 future.add(executors.submit(new TaskWithResult(i))); } for(Future<String> fs : future){ System.out.println(fs.get()); } } }
submit() 方法會返回 Future 對象,Future 對象存儲的也就是你返回的結果。你也可使用 isDone
來查詢 Future 是否已經完成。
影響任務行爲的一種簡單方式就是使線程 休眠,選定給定的休眠時間,調用它的 sleep()
方法, 通常使用的TimeUnit
這個時間類替換 Thread.sleep()
方法,示例以下:
public class SuperclassThread extends TestThread{ @Override public void run() { System.out.println(Thread.currentThread() + "starting ..." ); try { for(int i = 0;i < 5;i++){ if(i == 3){ System.out.println(Thread.currentThread() + "sleeping ..."); TimeUnit.MILLISECONDS.sleep(1000); } } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "wakeup and end ..."); } public static void main(String[] args) { ExecutorService executors = Executors.newCachedThreadPool(); for(int i = 0;i < 5;i++){ executors.execute(new SuperclassThread()); } executors.shutdown(); } }
關於 TimeUnit 中的 sleep() 方法和 Thread.sleep() 方法的比較,請參考下面這篇博客
上面提到線程調度器對每一個線程的執行都是不可預知的,隨機執行的,那麼有沒有辦法告訴線程調度器哪一個任務想要優先被執行呢?你能夠經過設置線程的優先級狀態,告訴線程調度器哪一個線程的執行優先級比較高,"請給這個騎手立刻派單",線程調度器傾向於讓優先級較高的線程優先執行,然而,這並不意味着優先級低的線程得不到執行,也就是說,優先級不會致使死鎖的問題。優先級較低的線程只是執行頻率較低。
public class SimplePriorities implements Runnable{ private int priority; public SimplePriorities(int priority) { this.priority = priority; } @Override public void run() { Thread.currentThread().setPriority(priority); for(int i = 0;i < 100;i++){ System.out.println(this); if(i % 10 == 0){ Thread.yield(); } } } @Override public String toString() { return Thread.currentThread() + " " + priority; } public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); for(int i = 0;i < 5;i++){ service.execute(new SimplePriorities(Thread.MAX_PRIORITY)); } service.execute(new SimplePriorities(Thread.MIN_PRIORITY)); } }
toString() 方法被覆蓋,以便經過使用 Thread.toString()
方法來打印線程的名稱。你能夠改寫線程的默認輸出,這裏採用了 Thread[pool-1-thread-1,10,main] 這種形式的輸出。
經過輸出,你能夠看到,最後一個線程的優先級最低,其他的線程優先級最高。注意,優先級是在 run 開頭設置的,在構造器中設置它們不會有任何好處,由於這個時候線程尚未執行任務。
儘管JDK有10個優先級,可是通常只有MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY 三種級別。
咱們上面提過,若是知道一個線程已經在 run() 方法中運行的差很少了,那麼它就能夠給線程調度器一個提示:我已經完成了任務中最重要的部分,可讓給別的線程使用CPU了。這個暗示將經過 yield() 方法做出。
有一個很重要的點就是,Thread.yield() 是建議執行切換CPU,而不是強制執行CPU切換。
對於任何重要的控制或者在調用應用時,都不能依賴於 yield()
方法,實際上, yield() 方法常常被濫用。
後臺(daemon) 線程,是指運行時在後臺提供的一種服務線程,這種線程不是屬於必須的。當全部非後臺線程結束時,程序也就中止了,**同時會終止全部的後臺線程。**反過來講,只要有任何非後臺線程還在運行,程序就不會終止。
public class SimpleDaemons implements Runnable{ @Override public void run() { while (true){ try { TimeUnit.MILLISECONDS.sleep(100); System.out.println(Thread.currentThread() + " " + this); } catch (InterruptedException e) { System.out.println("sleep() interrupted"); } } } public static void main(String[] args) throws InterruptedException { for(int i = 0;i < 10;i++){ Thread daemon = new Thread(new SimpleDaemons()); daemon.setDaemon(true); daemon.start(); } System.out.println("All Daemons started"); TimeUnit.MILLISECONDS.sleep(175); } }
在每次的循環中會建立10個線程,並把每一個線程設置爲後臺線程,而後開始運行,for循環會進行十次,而後輸出信息,隨後主線程睡眠一段時間後中止運行。在每次run 循環中,都會打印當前線程的信息,主線程運行完畢,程序就執行完畢了。由於 daemon
是後臺線程,沒法影響主線程的執行。
可是當你把 daemon.setDaemon(true)
去掉時,while(true) 會進行無限循環,那麼主線程一直在執行最重要的任務,因此會一直循環下去沒法中止。
按須要建立線程的對象。使用線程工廠替換了 Thread 或者 Runnable 接口的硬鏈接,使程序可以使用特殊的線程子類,優先級等。通常的建立方式爲
class SimpleThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { return new Thread(r); } }
Executors.defaultThreadFactory 方法提供了一個更有用的簡單實現,它在返回以前將建立的線程上下文設置爲已知值
ThreadFactory 是一個接口,它只有一個方法就是建立線程的方法
public interface ThreadFactory { // 構建一個新的線程。實現類可能初始化優先級,名稱,後臺線程狀態和 線程組等 Thread newThread(Runnable r); }
下面來看一個 ThreadFactory 的例子
public class DaemonThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); return t; } } public class DaemonFromFactory implements Runnable{ @Override public void run() { while (true){ try { TimeUnit.MILLISECONDS.sleep(100); System.out.println(Thread.currentThread() + " " + this); } catch (InterruptedException e) { System.out.println("Interrupted"); } } } public static void main(String[] args) throws InterruptedException { ExecutorService service = Executors.newCachedThreadPool(new DaemonThreadFactory()); for(int i = 0;i < 10;i++){ service.execute(new DaemonFromFactory()); } System.out.println("All daemons started"); TimeUnit.MILLISECONDS.sleep(500); } }
Executors.newCachedThreadPool
能夠接受一個線程池對象,建立一個根據須要建立新線程的線程池,但會在它們可用時重用先前構造的線程,並在須要時使用提供的ThreadFactory建立新線程。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }
一個線程能夠在其餘線程上調用 join()
方法,其效果是等待一段時間直到第二個線程結束才正常執行。若是某個線程在另外一個線程 t 上調用 t.join() 方法,此線程將被掛起,直到目標線程 t 結束纔回復(能夠用 t.isAlive() 返回爲真假判斷)。
也能夠在調用 join 時帶上一個超時參數,來設置到期時間,時間到期,join方法自動返回。
對 join 的調用也能夠被中斷,作法是在線程上調用 interrupted
方法,這時須要用到 try...catch 子句
public class TestJoinMethod extends Thread{ @Override public void run() { for(int i = 0;i < 5;i++){ try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { System.out.println("Interrupted sleep"); } System.out.println(Thread.currentThread() + " " + i); } } public static void main(String[] args) throws InterruptedException { TestJoinMethod join1 = new TestJoinMethod(); TestJoinMethod join2 = new TestJoinMethod(); TestJoinMethod join3 = new TestJoinMethod(); join1.start(); // join1.join(); join2.start(); join3.start(); } }
join() 方法等待線程死亡。 換句話說,它會致使當前運行的線程中止執行,直到它加入的線程完成其任務。
因爲線程的本質,使你不能捕獲從線程中逃逸的異常,一旦異常逃出任務的run 方法,它就會向外傳播到控制檯,除非你採起特殊的步驟捕獲這種錯誤的異常,在 Java5 以前,你能夠經過線程組來捕獲,可是在 Java5 以後,就須要用 Executor 來解決問題,由於線程組不是一次好的嘗試。
下面的任務會在 run 方法的執行期間拋出一個異常,而且這個異常會拋到 run 方法的外面,並且 main 方法沒法對它進行捕獲
public class ExceptionThread implements Runnable{ @Override public void run() { throw new RuntimeException(); } public static void main(String[] args) { try { ExecutorService service = Executors.newCachedThreadPool(); service.execute(new ExceptionThread()); }catch (Exception e){ System.out.println("eeeee"); } } }
爲了解決這個問題,咱們須要修改 Executor 產生線程的方式,Java5 提供了一個新的接口 Thread.UncaughtExceptionHandler
,它容許你在每一個 Thread 上都附着一個異常處理器。Thread.UncaughtExceptionHandler.uncaughtException()
會在線程因未捕獲臨近死亡時被調用。
public class ExceptionThread2 implements Runnable{ @Override public void run() { Thread t = Thread.currentThread(); System.out.println("run() by " + t); System.out.println("eh = " + t.getUncaughtExceptionHandler()); // 手動拋出異常 throw new RuntimeException(); } } // 實現Thread.UncaughtExceptionHandler 接口,建立異常處理器 public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{ @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("caught " + e); } } public class HandlerThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { System.out.println(this + " creating new Thread"); Thread t = new Thread(r); System.out.println("created " + t); t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); System.out.println("ex = " + t.getUncaughtExceptionHandler()); return t; } } public class CaptureUncaughtException { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(new HandlerThreadFactory()); service.execute(new ExceptionThread2()); } }
在程序中添加了額外的追蹤機制,用來驗證工廠建立的線程會傳遞給UncaughtExceptionHandler
,你能夠看到,未捕獲的異常是經過 uncaughtException
來捕獲的。
文章來源:
《Java編程思想》
https://www.javatpoint.com/join()-method
下面爲本身作個宣傳,歡迎關注公衆號 Java建設者
,號主是Java技術棧,熱愛技術,喜歡閱讀,熱衷於分享和總結,但願能把每一篇好文章分享給成長道路上的你。 關注公衆號回覆 002
領取爲你特地準備的大禮包,你必定會喜歡並收藏的。
本文由博客一文多發平臺 OpenWrite 發佈!