標籤:多線程 並發 java編程思想
這幾天開始學習java多線程並發編程的內容了,以前也學習過多線程的知識,但是總是覺得學的不是很清楚;希望這一次學習《java編程思想》能讓自己對並發,多線程的概念有一個更加深入的瞭解。這一章估計要寫好幾篇部落格了,這篇部落格是對於基礎的一個總結,主要內容是對啟動一個線程的幾種方式和對線程一些操作函數的總結。
首先來瞭解一下多線程的概念,多線程看起來同一時刻在同時運行多個任務,但是從作業系統的層面來講只是讓多個任務以極快的速度進行切換而已,一個時刻實際上還是只有一個任務在cpu上啟動並執行。Java中的多線程靠的是Thread類實現的,我們將任務交給Thread類的對象,然後它就會以多線程的方式來運行我們的任務。Thread類中有一個run()函數,這個函數體中放我們需要執行的任務。
我們可以有三種方式來啟動一個線程。第一是直接繼承Thread類,實現裡面的run()方法;這種方法最簡單,但是這樣就不能再繼承別的類了。第二種方法是實現Runnable介面,我們也需要實現裡面的run()方法;但是現在它還是沒有任何的線程能力的,要實現線程行為,你必須顯示的將一個任務附線上程上實現,具體來說就是將一個Runnable對象傳給一個Thread對象。第三種方法是實現Callable介面,這個介面的內容和Runnable介面並沒有什麼不同,不同點在於Callable介面是可以有傳回值的,我們可以顯式的捕獲這個傳回值。
///通過繼承Thread實現一個線程類public class FirstThread extends Thread{ private int i; public void run(){ for(int i=0;i<10;i++){ ///getName()是Thread類的方法,因為繼承了Thread類 ///所以可以直接調用 System.out.println(getName()+" "+i); } } public static void main(String[] args){ for(int i=0;i<10;i++){ //一般情況可以通過Thread.currentThread取得當前的線程 System.out.println(Thread.currentThread().getName()+" "+i); if(i==1){ ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new FirstThread()); //添加一個新任務 exec.shutdown(); ///防止後面再有新任務被添加進來 } } }/*Output main 0 main 1 main 2 Thread-0 0 Thread-0 1 Thread-0 2 Thread-0 3 Thread-0 4 Thread-0 5 Thread-0 6 Thread-0 7 Thread-0 8 Thread-0 9 main 3 main 4 main 5 main 6 main 7 main 8 main 9 */}
package lkl;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;//通過實現Runnable介面來實現一個線程類public class SecondThread implements Runnable{ private int i; ///同樣要實現run()方法 public void run(){ for(int i=0; i<10 ;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } } public static void main(String[] args){ for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); if(i==1){ ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new Thread(new SecondThread(),"新線程1")); exec.shutdown(); } }/* main 0 main 1 pool-1-thread-1 0 pool-1-thread-1 1 pool-1-thread-1 2 pool-1-thread-1 3 pool-1-thread-1 4 pool-1-thread-1 5 pool-1-thread-1 6 pool-1-thread-1 7 pool-1-thread-1 8 pool-1-thread-1 9 main 2 main 3 main 4 main 5 main 6 main 7 main 8 main 9 */ }}
package lkl;//通過繼承Callable介面來實現線程類//Callable介面和FutureTask介面接合使用//我們使用FutureTask對Callable進行封裝,然後//就可以通過FutureTask對象獲得線程的傳回值,///在這種情況下,也可以捕獲線程拋出的異常。//另外注意:這兩個類都是有泛型限制的,具體的類型和call()方法的傳回值一樣import java.util.concurrent.Callable;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.FutureTask;public class ThirdThread implements Callable<Integer>{ private int i; ///實現call()方法,作為線程執行體 public Integer call(){ for(i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } return i; } public static void main(String[] args){ ThirdThread rt = new ThirdThread(); //使用FutureTask來封裝Callable對象 FutureTask<Integer> task = new FutureTask(rt); for(int i=0; i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); if(i==1){ ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(task); exec.shutdown(); } } try{ //擷取線程傳回值,如果線程還沒執行完,則會阻塞直到取得傳回值 System.out.println("線程的傳回值:"+task.get()); }catch(Exception ex){ ex.printStackTrace(); } }/* main 0 main 1 pool-1-thread-1 0 pool-1-thread-1 1 pool-1-thread-1 2 pool-1-thread-1 3 main 2 main 3 main 4 main 5 main 6 main 7 main 8 main 9 pool-1-thread-1 4 pool-1-thread-1 5 pool-1-thread-1 6 pool-1-thread-1 7 pool-1-thread-1 8 pool-1-thread-1 9 線程的傳回值:10 */}
然後在上面的代碼中我們看到了一個Executor對象,這裡解釋下:Executor(執行器)是從Java5開始提供的一個管理Thread對象的類,Executor對象在用戶端和任務執行之間提供了一個間階層;與用戶端直接執行任務不同,任務由這個中介對象執行。Executor允許你管理非同步任務的執行,而無需顯示的管理線程的生命週期。具體的步驟是建立一個ExecutorService對象,然後將線程放入這個對象中去運行,對應的ExecutorService對象三種:第一是前面用的CachedThreadPool,會在程式中建立與所需數量相同的線程。第二是FixedThreadPool,可以一次性建立指定數目的線程(通過在構造器中指定數目實現)。第三種是SingleThreadExecutor,這種執行器每次只能執行一個線程;如果向其提交了多個任務,那麼這些任務將排隊,依次執行。
下面簡要的介紹一下幾種控制線程的方法(函數)
(1).join()方法
join()方法意味著讓一個線程等待另一個線程。如果我在現在的線程上調用t.join(),那麼當前線程就會阻塞,直到t線程完成為止;join()有一個重載的方法可以指定阻塞的時間。
package lkl;///join()方法的使用public class JoinThread extends Thread{ public JoinThread(String name){ super(name); } public void run(){ for(int i=0;i<10;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args) throws Exception{ for(int i=0; i<10;i++){ if(i==1){ JoinThread jt = new JoinThread("被Join的線程"); jt.start(); //main線程調用了jt線程的Join()方法,則main線程 //只有等jt結束後,main線程才會繼續進行 jt.join(); } System.out.println(Thread.currentThread().getName()); } }}
(2).setDaemon(true)方法
這個方法意味著將當前線程設為後台線程。後台線程是在後台啟動並執行,它的任務是為其它的線程提供服務(如JVM的記憶體回收機制就是典型的後台線程)。後台線程的特點在於如果所有的前台線程都死亡了,那麼後台線程就會自動死亡。
package lkl;public class DaemonThread extends Thread{ public void run(){ for(int i=0;i<10;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args) throws Exception{ DaemonThread dt = new DaemonThread(); dt.setDaemon(true); //設為後台線程,需要在啟動之前設定 dt.start(); for(int i=0; i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } //可以看到在main線程結束自後,Daemon線程也結束了 }}
package lkl;import java.util.concurrent.*;//後台線程會在不執行finally子句的前提下//就會終止其run()方法class Adaemon implements Runnable{ public void run(){ try{ System.out.println("Starting ADamon"); TimeUnit.SECONDS.sleep(1); } catch(InterruptedException e){ System.out.println("err"); } finally{ System.out.println("This should always run?"); } }}public class DaemonsDontRunFinally { public static void main(String[] args){ Thread t = new Thread(new Adaemon()); t.setDaemon(true); t.start(); }}
(3).線程睡眠sleep()
sleep()的功能是很簡單的,只是簡單的讓當前正在執行的線程一段時間(我們可以指定這段時間),並進入阻塞狀態。sleep()是Thread類的一個靜態方法。
(4).線程讓步yield()
yield()和sleep()有點類似,也是讓當前正在執行的線程暫停,但是它不會阻塞當前線程,而是讓其進入就緒狀態。然後在進行一次調度,調取一個優先順序相同或更高的線程進行執行。指的注意的是yield()只是一個建議性的方法,它並不能完全保證當前線程讓步,所以我們應該更多的使用sleep()而不是yield()。
(5).setPriority()改變線程的優先順序
一般來說優先順序越高的線程其獲得執行的機會就越大,但這是依賴於具體的作業系統的;而且不同的作業系統的優先順序的層級劃分也是不同的。所以Java提供了三個靜態常量來表示優先順序:MAX_PRIORITY 表示最高優先順序。MIN_PRIORITY表示最低優先順序。NORM_PRIORITY表示一般的優先順序;如果我們要設定線程的優先順序,我們應該盡量選擇這三個常量,而不是其它的數字。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Thinking in Java--Java多線程學習筆記(1)