併發編程式JAVA的一個相對較複雜的模塊。縱觀各招聘網站的要求,紛紛要求要具有高併發,高性能開發經驗。這一塊的知識,不得不學啊。學習併發有斷斷續續,有很長一段時間了,相關併發書籍也看了很多。感受有些收穫,特開系列文章,分享下本身的學習收穫。html
線程的內容涉及的內容特別多,僅幾篇文章不可能透徹闡明,我的又不喜歡弄個大塊頭文章,因此仍是作個系列文章吧。做爲系列的第一篇,本章主要內容:
java
線程VS進程web
並行VS併發數據庫
線程的建立和啓動
編程
user thread VS daemon thread
tomcat
返回結果類型的線程安全
join操做多線程
進程有獨立的地址空間,對應於系統的程序和軟件。好比QQ,word java tomcat web應用,一個進程崩潰,不會影響其餘進程。屬於重量級的資源管理方式。進程切換時,耗費資源較大,效率要差一些。併發
線程ide
而線程只是一個進程中的不一樣執行路徑。一個進程,能夠包含多個線程。線程有本身的堆棧和局部變量,而多個線程共享內存。線程不可以獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。通常狀況下main()做爲應用程序的入口。屬於輕量級別的資源管理方式。
2.並行VS併發
併發:一個處理器同時處理多個任務。經過CPU分片執行任務,屬於邏輯上的同時發生。好比:一我的同時吃三個饅頭。
並行:多個處理器或者是多核的處理器同時處理多個不一樣的任務。物理上的同時發生。好比:三我的同時吃三個饅頭。
並行的前提:具有多個處理器和多核的處理資源。
併發的前提:在資源CPU能力必定的基礎上,區分I/O密集型任務,CPU密集型任務,儘可能減小CPU等待時間,壓榨CPU的處理極限。
JAVA領域,涉及的都是併發。
3.線程的建立和啓動
JAVA的線程有2中方式
1.實現 java.lang.Runnable接口
2.繼承java.lang.Thread類
HeavyWorkRunnable.java,實現 java.lang.Runnable接口
package com.threadexample.base; public class HeavyWorkRunnable implements Runnable { @Override public void run() { System.out.println("Doing heavy processing - START "+Thread.currentThread().getName()); try { Thread.sleep(1000); //Get database connection, delete unused data from DB doDBProcessing(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Doing heavy processing - END "+Thread.currentThread().getName()); } private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } }
MyThread.java,繼承java.lang.Thread類
package com.threadexample.base; public class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { System.out.println("MyThread - START "+Thread.currentThread().getName()); try { Thread.sleep(1000); //Get database connection, delete unused data from DB doDBProcessing(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("MyThread - END "+Thread.currentThread().getName()); } private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } }
測試代碼ThreadRunExample
package com.threadexample.base; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadRunExample { public static void main(String[] args){ Thread t1 = new Thread(new HeavyWorkRunnable(), "t1"); Thread t2 = new Thread(new HeavyWorkRunnable(), "t2"); System.out.println("Starting Runnable threads"); t1.start(); t2.start(); System.out.println("Runnable Threads has been started"); Thread t3 = new MyThread("t3"); Thread t4 = new MyThread("t4"); System.out.println("Starting MyThreads"); t3.start(); t4.start(); System.out.println("MyThreads has been started"); ExecutorService executorService = Executors.newCachedThreadPool(); Thread t5 = new Thread(new HeavyWorkRunnable(), "t5"); HeavyWorkRunnable t6 = new HeavyWorkRunnable(); executorService.submit(t5);//啓動 executorService.execute(t6);//啓動 executorService.shutdown(); } }
代碼展現了2種建立線程的方式,以及多種啓動方式。須要注意的是ExecutorService API,ExecutorService 的設計是爲了儘量實現線程的複用,由於線程也會佔用資源,線程複用能夠下降資源佔用,提高性能,是併發優化的一個途徑,固然使用ThreadFactory的工廠,爲線程管理提供了便利。
Runnable vs Thread
1.不只僅提供執行任務,還提供額外功能,實現 java.lang.Runnable接口
2.若是類的惟一功能是做爲線程執行,能夠繼承java.lang.Thread
須要注意,thread 提供的方法沒有返回值。
------------------------------
執行結果
Starting Runnable threads
Runnable Threads has been started
Starting MyThreads
MyThreads has been started
MyThread - START t3
Doing heavy processing - START t2
MyThread - START t4
Doing heavy processing - START t1
Doing heavy processing - START pool-1-thread-1
Doing heavy processing - START pool-1-thread-2
MyThread - END t3
Doing heavy processing - END t1
MyThread - END t4
Doing heavy processing - END t2
Doing heavy processing - END pool-1-thread-1
Doing heavy processing - END pool-1-thread-2
經過結果分析:線程實際執行結果,和程序編寫順序不一致。並且屢次運行執行的結果也不一樣。這就是多線程,執行結果的不肯定性。
4.user thread VS daemon thread
對上面的ThreadRunExample ,進行DEBUG跟蹤。
threads有些是本身建立的,有些不是本身建立的,如Finalizer線程,負責垃圾收集線程,是虛擬機自動建立的線程。
導出thread信息,部分信息截圖
經過兩個線程對比,Finalizer是daemon線程,優先級是8. 屬於 「user thread"
咱們建立的線程不是daemon線程,優先級是5.屬於"daemon thread".
所謂守護線程(daemon thread),是指在程序運行的時候在後臺提供一種通用服務的線程,好比垃圾回收線程就是一個很稱職的守護者,而且這種線程並不屬於程序中不可或缺的部分。因 此,當全部的非守護線程結束時,程序也就終止了,同時會殺死進程中的全部守護線程。反過來講,只要任何非守護線程還在運行,程序就不會終止。
用戶線程和守護線程二者幾乎沒有區別,惟一的不一樣之處就在於退出時機的差異:若是用戶線程已經所有退出運行了,只剩下守護線程存在了,虛擬機也就退出了。 由於沒有了被守護者,守護線程也就沒有工做可作了,也就沒有繼續運行程序的必要了。
將線程轉換爲守護線程能夠經過調用Thread對象的setDaemon(true)方法來實現。在使用守護線程時須要注意一下幾點:
(1) thread.setDaemon(true)必須在thread.start()以前設置,不然會跑出一個IllegalThreadStateException異常。你不能把正在運行的常規線程設置爲守護線程。
(2) 在Daemon線程中產生的新線程也是Daemon的。
(3) 守護線程應該永遠不去訪問固有資源,如文件、數據庫,由於它會在任什麼時候候甚至在一個操做的中間發生中斷。
JavaDaemonThread.java代碼
package com.threadexample.base; public class JavaDaemonThread { public static void main(String[] args) throws InterruptedException { Thread dt = new Thread(new DaemonThread(), "dt"); dt.setDaemon(true); dt.start(); //continue program Thread.sleep(30000); System.out.println("Finishing program"); } } class DaemonThread implements Runnable{ //此處是個死循環(若是不是daemon線程,他會一直執行下去,永不退出; //對於daemon線程,它會隨user thread一併退出 @Override public void run() { while(true){ processSomething(); } } private void processSomething() { try { System.out.println("Processing daemon thread"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }
執行結果
Processing daemon thread
Processing daemon thread
Processing daemon thread
Processing daemon thread
Processing daemon thread
Processing daemon thread
Finishing program
5.返回結果類型的線程(Callable Future)
下面是Runnable 接口.執行方法沒有返回值。
public interface Runnable { public abstract void run(); }
若是在面對執行計算任務是,須要有返回值怎麼辦?
也是能夠的,之前面的例子稍加改造。
HeavyWorkRunnableWithResult.java
package com.threadexample.base; public class HeavyWorkRunnableWithResult implements Runnable { private String result; public String getResult(){ return result; } @Override public void run() { System.out.println("Doing heavy processing - START "+Thread.currentThread().getName()); try { Thread.sleep(1000); //Get database connection, delete unused data from DB doDBProcessing(); this.result="執行結果="+Thread.currentThread().getName(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Doing heavy processing - END "+Thread.currentThread().getName()); } private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } }
測試代碼ThreadRunExampleWithResult.java
package com.threadexample.base; public class ThreadRunExampleWithResult { public static void main(String[] args){ System.out.println("Runnable Threads has been started"); HeavyWorkRunnableWithResult runnableWithResult = new HeavyWorkRunnableWithResult(); Thread t1 = new Thread(runnableWithResult,"t1"); t1.start(); System.out.println("輸出結果:"+runnableWithResult.getResult()); } }
打印結果
------------------------
Runnable Threads has been started
輸出結果:null
Doing heavy processing - START t1
Doing heavy processing - END t1
--------------------------
經測試結果不是咱們想要的結果,返回值爲空。這是由於在打印輸出結果時,線程尚未執行完。
怎麼修復這個問題呢。
升級後的HeavyWorkRunnableWithResult.java,添加了個完成標記字段。
package com.threadexample.base; public class HeavyWorkRunnableWithResult implements Runnable { private String result; private volatile boolean done=false; public String getResult(){ return result; } public boolean isDone(){ return this.done; } @Override public void run() { System.out.println("Doing heavy processing - START "+Thread.currentThread().getName()); try { Thread.sleep(1000); //Get database connection, delete unused data from DB doDBProcessing(); this.result="執行結果="+Thread.currentThread().getName(); this.done=true; } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Doing heavy processing - END "+Thread.currentThread().getName()); } private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } }
注意必定要將done 設置爲volatile 類型,不然main線程永遠不會讀取不到更新的值。關於volatile
,又涉及到底層的JMM模型了,不在此涉及了。
改造後的ThreadRunExampleWithResult.java,加個循環,保證有結果以後打打印。
package com.threadexample.base; public class ThreadRunExampleWithResult { public static void main(String[] args){ System.out.println("Runnable Threads has been started"); HeavyWorkRunnableWithResult runnableWithResult = new HeavyWorkRunnableWithResult(); Thread t1 = new Thread(runnableWithResult,"t1"); t1.start(); while(!runnableWithResult.isDone()){ //空循環 } System.out.println("輸出結果:"+runnableWithResult.getResult()); } }
執行結果
-----------------------------
Runnable Threads has been started
Doing heavy processing - START t1
Doing heavy processing - END t1
輸出結果:執行結果=t1
-----------------------------
仔細想一想,咱們作了好多無用的事,好比完成判斷,循環等待。JAVA專爲解決此類問題,提供了Callable接口。
public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
HeavyWorkCallable.java 做爲HeavyWorkRunnableWithResult 的Callable版本
package com.threadexample.base; import java.util.concurrent.Callable; public class HeavyWorkCallable implements Callable<String> { @Override public String call() { System.out.println("Doing heavy processing - START "+Thread.currentThread().getName()); try { Thread.sleep(1000); //Get database connection, delete unused data from DB doDBProcessing(); return "執行結果="+Thread.currentThread().getName(); } catch (InterruptedException e) { e.printStackTrace(); } return null; } private void doDBProcessing() throws InterruptedException { Thread.sleep(5000); } }
ThreadRunExampleCallable.java 測試類
package com.threadexample.base; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.*; public class ThreadRunExampleCallable { public static void main(String[] args){ System.out.println("Runnable Threads has been started"); //Get ExecutorService from Executors utility class, thread pool size is 10 ExecutorService executor = Executors.newFixedThreadPool(5); //create a list to hold the Future object associated with Callable List<Future<String>> list = new ArrayList<Future<String>>(); //Create MyCallable instance Callable callable = new HeavyWorkCallable(); for(int i=0; i< 10; i++){ //submit Callable tasks to be executed by thread pool Future<String> future = executor.submit(callable); //add Future to the list, we can get return value using Future list.add(future); } for(Future<String> fut : list){ try { //print the return value of Future, notice the output delay in console // because Future.get() waits for task to get completed System.out.println(new Date()+ "::"+fut.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } //shut down the executor service now executor.shutdown(); // System.out.println("輸出結果:"+runnableWithResult.getResult()); } }
執行結果以下
-----------------------
Runnable Threads has been started
Doing heavy processing - START pool-1-thread-1
Doing heavy processing - START pool-1-thread-2
Doing heavy processing - START pool-1-thread-3
Doing heavy processing - START pool-1-thread-5
Doing heavy processing - START pool-1-thread-4
Doing heavy processing - START pool-1-thread-3
Sun Jun 26 17:41:51 CST 2016::執行結果=pool-1-thread-1
Doing heavy processing - START pool-1-thread-5
Sun Jun 26 17:41:57 CST 2016::執行結果=pool-1-thread-2
Doing heavy processing - START pool-1-thread-1
Sun Jun 26 17:41:57 CST 2016::執行結果=pool-1-thread-3
Doing heavy processing - START pool-1-thread-4
Doing heavy processing - START pool-1-thread-2
Sun Jun 26 17:41:57 CST 2016::執行結果=pool-1-thread-4
Sun Jun 26 17:41:57 CST 2016::執行結果=pool-1-thread-5
Sun Jun 26 17:41:57 CST 2016::執行結果=pool-1-thread-3
Sun Jun 26 17:42:03 CST 2016::執行結果=pool-1-thread-1
Sun Jun 26 17:42:03 CST 2016::執行結果=pool-1-thread-5
Sun Jun 26 17:42:03 CST 2016::執行結果=pool-1-thread-2
Sun Jun 26 17:42:03 CST 2016::執行結果=pool-1-thread-4
Process finished with exit code 0
-----------------------
6.join操做
Java Thread join方法能夠用於直到指定線程死亡時中止當前線程。有三個重載方法
public final void join()
public final synchronized void join(long millis): 中止當前線程直到線程死時或者超過millis時間。
public final synchronized void join(long millis, int nanos)
咱們能夠對5節中的ThreadRunExampleWithResult,進行改造下,達到想要的效果
package com.threadexample.base; public class ThreadRunExampleJoin { public static void main(String[] args){ System.out.println("Runnable Threads has been started"); HeavyWorkRunnableWithResult runnableWithResult = new HeavyWorkRunnableWithResult(); Thread t1 = new Thread(runnableWithResult,"t1"); t1.start(); // while(!runnableWithResult.isDone()){ // //空循環 // } try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("輸出結果:"+runnableWithResult.getResult()); } }
join方法通常使用在「直到....才」的場景。和自帶的CountDownLatch工具類有重合的部分。
輸出結果
----------
Runnable Threads has been started
Doing heavy processing - START t1
Doing heavy processing - END t1
輸出結果:執行結果=t1
---------
引用資源
http://www.journaldev.com/1024/java-thread-join-example
http://www.cnblogs.com/luochengor/archive/2011/08/11/2134818.html
http://www.cnblogs.com/lmule/archive/2010/08/18/1802774.html
最後
本文主要講了線程的幾點用法,比較簡單。
Runnable ->Thread->Daemon ->Callable-->Join。捎帶着提了下violatile關鍵字。
因爲線程間沒有共享變量,因此不涉及到一丁點線程安全的東西。後面專門抽出幾篇講解下。
附件爲代碼