java線程和線程池
一、創建多線程的方式
????java多線程非經常見。怎樣使用多線程,怎樣創建線程。java中有兩種方式,第一種是讓自己的類實現Runnable接口。另外一種是讓自己的類繼承Thread類。事實上Thread類自己也是實現了Runnable接口。
詳細使用實比例如以下:
1、通過實現Runnable接口方式
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | publicclassMyThread1?implementsRunnable//通過實現Runnable接口方式 { ????String sign =?"thread#1@"; ????@Override ????publicvoidrun() ????{ ????????for(inti =?0; i <?30; i++) ????????{ ????????????System.out.println(sign +?"->"+ i); ????????????try ????????????{ ????????????????Thread.sleep(100L); ????????????}?catch(InterruptedException e) ????????????{ ????????????????e.printStackTrace(); ????????????} ????????} ????} } |
|
2、通過繼承Thread類的方式
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | publicclassMyThread2?extendsThread//通過繼承Thread類的方式 { ????String sign =?"thread#2@"; ????@Override ????publicvoidrun() ????{ ????????for(inti =?0; i <?30; i++) ????????{ ????????????System.out.println(sign +?"->"+ i); ????????????try ????????????{ ????????????????Thread.sleep(100L); ????????????}?catch(InterruptedException e) ????????????{ ????????????????e.printStackTrace(); ????????????} ????????} ????} } |
|
????再啟用上面創建的兩種線程,調運代碼例如以下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | publicstaticvoidmain(String[] args)?throwsInterruptedException { ????longstartTime = System.currentTimeMillis(); ????// 通過主線程啟動自己的線程 ????// 通過實現runnable接口 ????Runnable myThread1 =?newMyThread1(); ????Thread thread1 =?newThread(myThread1); ????thread1.start(); ????// 通過繼承thread類 ????Thread thread2 =?newThread(newMyThread2()); ????thread2.start(); ????// 注意這里不是調運run()方法,而是調運線程類Thread的start方法。在Thread方法內部。會調運本地系統方法。終于會自己主動調運自己線程類的run方法 ????// 讓主線程睡眠 ????Thread.sleep(1000L); ????System.out.println("主線程結束!用時:" ????????????+ (System.currentTimeMillis() - startTime)); ????// System.exit(0); } |
|
????輸入結果(每次輸入可能不同)不再具體列出。對于上面的兩種方式。更推薦使用實現Runnable接口的方式實現多線程。一方面是能夠防止java單繼承的顧慮。還有一方面Runnable是面向接口編程。擴展性比起繼承Thread更好。
所以盡量使用implement Runnable的方式。
?
二、java線程類型說明
????上面是將多線程跑起來了,可是有個問題。假設不讓主線程睡眠。當主線程(比方main線程)結束以后。假設子線程還沒結束。那么子線程是否還會執行呢?答案是會繼續執行,為了說明這個問題。就又涉及到java中線程的類型。
java中線程一共同擁有兩種類型:守護線程(daemon thread)和用戶線程(user thread)又叫非守護線程。
能夠通過thread.setDaemon()方法設置線程是否為守護線程。默認是設置非守護線程(false)。java虛擬機停止執行的時間是虛擬機中執行的全部線程都是守護線程的時候,也就是說假設jvm中沒有user thread的時候,jvm就停止執行。或者說jvm在最后一個非守護線程結束的時候,將停止全部的守護進程,然后退出jvm。
????當使用main方法開啟線程時,主線程默認是非守護進程,而用戶自己開的進程也是非守護進程。
當主線程結束,可是子線程(默認是非守護線程)還沒結束,所以虛擬機是不停止執行的,當子線程執行完以后。假設主線程也執行完成,jvm發現沒有非守護線程,就將jvm關閉,所以當main方法的主線程執行完成以后,子線程是會繼續執行的。
當然我們也能夠讓在main主線程執行完成以后,子線程不再執行。方法就是將全部的子線程設置為守護進程setDaemon(true)就可以。
可是須要注意的是這個設置須要在線程執行之前設置,不能在線程執行的過程中改動線程類型。
????更直白點說假設用戶將線程設置為守護進程。那實際的意思就是告訴jvm你不用搭理我這個線程,jvm想停止的時候,不用考慮我這個線程是否運行結束。這樣的線程詳細的使用比方在垃圾回收機制的線程。就是一個守護線程。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | Runnable myThread1 =?newMyThread1(); Thread thread1 =?newThread(myThread1); thread1.setDaemon(true); thread1.start(); Thread thread2 =?newThread(newMyThread2()); thread2.setDaemon(false); thread2.start(); System.out.println("mainThread isDaemon:" ????????+ Thread.currentThread().isDaemon()); System.out.println("thread1 isDaemon:"+ thread1.isDaemon()); System.out.println("thread2 isDaemon:"+ thread2.isDaemon()); |
|
?
三、線程池的使用
????上面已經看過了能夠在代碼中直接新起線程。假設我們在主線程中新起一百個線程。讓這一百個線程同一時候工作,邏輯上是沒有不論什么問題的。可是這樣做對系統資源的開銷非常大,這樣會在短時間內處理非常多的任務,當然包含新起線程等等。基于這種考慮,我們是有必要引入線程池這個東西的。線程池就是一個池子,池子里有非常多可用線程資源,假設須要就直接從這個池子里拿就是。
當不用的時候。放入池子中,線程池會自己主動幫我們管理。所以使用線程池主要有下面兩個優點:1、降低在創建和銷毀線程上所花的時間以及系統資源的開銷?2、如不使用線程池,有可能造成系統創建大量線程而導致消耗完系統內存?。
????假設我們想要使用線程池,就須要先定義這個線程池。定義線程池的時候,當中的幾個主要參數說明例如以下:
-corePoolSize(int):線程池中保持的線程數量。包含空暇線程在內。也就是線程池釋放的最小線程數量界限。
-maximumPoolSize(int):線程池中嫩容納最大線程數量。
-keepAliveTime(long):空暇線程保持在線程池中的時間。當線程池中線程數量大于corePoolSize的時候。
-unit(TimeUnit枚舉類):上面參數時間的單位。能夠是分鐘。秒,毫秒等等。
-workQueue(BlockingQueue<Runnable>):任務隊列,當線程任務提交到線程池以后,首先放入隊列中,然后線程池依照該任務隊列依次運行對應的任務。
能夠使用的workQueue有非常多,比方:LinkedBlockingQueue等等。
-threadFactory(ThreadFactory類):新線程產生工廠類。
-handler(RejectedExecutionHandler類):當提交線程拒絕運行、異常的時候。處理異常的類。
該類取值例如以下:(注意都是內部類)
ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,可是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務。然后又一次嘗試運行任務,反復此過程。
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務。 ???除了自己定義線程池以外,?java提供了幾種經常使用的線程池,能夠快捷的供程序猿使用,他們各自是:
1、newFixedThreadPool?創建固定大小數量線程池,數量通過傳入的參數決定。
2、newSingleThreadExecutor?創建一個線程容量的線程池,全部的線程依次運行,相當于創建固定數量為1的線程池。
3、newCachedThreadPool?創建可緩存的線程池,沒有最大線程限制(實際上是Integer.MAX_VALUE)。假設用空暇線程等待時間超過一分鐘,就關閉該線程。
4、newScheduledThreadPool?創建計劃(延遲)任務線程池,線程池中的線程能夠讓其在特定的延遲時間之后運行,也能夠以固定的時間反復運行(周期性運行)。
相當于曾經的Timer類的使用。
5、newSingleThreadScheduledExecutor?創建單線程池延遲任務,創建一個線程容量的計劃任務。
????事實上通過靜態方法創建的上面幾種線程池,也都是通過傳入默認的各個參數。然后返回一個有各自特點的線程池。詳細參數能夠通過查看jdk源代碼閱讀。
????有了線程池。那么我們怎樣利用線程池中線程運行我們的任務,因為Java將線程池的封裝,我們拿到的線程池的線程事實上是一個包括線程任務的運行器,僅僅須要調運運行器的運行方法,就會自己主動運行我們線程中的任務。對于非計劃任務,我們須要拿到一個ThreadPoolExecutor,對于計劃任務。我們須要拿到一個ScheduledThreadPoolExecutor(它是ThreadPoolExecutor的子類)。在了解這兩個類之前。須要先了解兩個接口,ExecutorService以及它的子接口ScheduleThreadExecutorService接口。上面兩個接口分別實現了這兩個接口,這個兩接口定義了execute(Runnable r)方法,這種方法去運行線程的任務。
也就是我們通過調運ThreadPoolExecutor或者ScheduledThreadPoolExecutor的execute(Runnable r)方法開啟我們的線程,而且運行我們的線程任務,詳細代碼例如以下:
????定義一個單例的線程池:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | publicclassMyPool { ????privatestaticMyPool myPool =?null; ????//單例線程池中有兩種詳細的線程池 ????privateThreadPoolExecutor threadPool =?null; ????privateScheduledThreadPoolExecutor scheduledPool =?null; ????publicThreadPoolExecutor getThreadPool() ????{ ????????returnthreadPool; ????} ????publicScheduledThreadPoolExecutor getScheduledPool() ????{ ????????returnscheduledPool; ????} ????//設置線程池的各個參數的大小 ????privateintcorePoolSize =?10;// 池中所保存的線程數,包含空暇線程。 ????privateintmaximumPoolSize =?20;// 池中同意的最大線程數。 ????privatelongkeepAliveTime =?3;// 當線程數大于核心時,此為終止前多余的空暇線程等待新任務的最長時間。 ????privateintscheduledPoolSize =?10; ????privatestaticsynchronizedvoidcreate() ????{ ????????if(myPool ==?null) ????????????myPool =?newMyPool(); ????} ????publicstaticMyPool getInstance() ????{ ????????if(myPool ==?null) ????????????create(); ????????returnmyPool; ????} ????privateMyPool() ????{ ????????//實例化線程池。這里使用的LinkedBlockingQueue作為workQueue。使用DiscardOldestPolicy作為handler ????????this.threadPool =?newThreadPoolExecutor(corePoolSize, maximumPoolSize, ????????????????keepAliveTime, TimeUnit.SECONDS, ????????????????newLinkedBlockingQueue<Runnable>(), ????????????????newThreadPoolExecutor.DiscardOldestPolicy()); ????????//實例化計劃任務線程池 ????????this.scheduledPool =?newScheduledThreadPoolExecutor(scheduledPoolSize); ????} } |
|
????獲取線程池中的線程,而且運行線程中任務:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | @Test publicvoidtestThreadPool() { ????ThreadPoolExecutor pool1 = (ThreadPoolExecutor) Executors ????????????.newCachedThreadPool(); ????pool1.execute(newRunnable() ????{ ????????@Override ????????publicvoidrun() ????????{ ????????????System.out.println("快捷線程池中的線程! " ); ????????} ????}); ????ThreadPoolExecutor pool2 = MyPool.getInstance().getThreadPool(); ????pool2.execute(newRunnable() ????{ ????????@Override ????????publicvoidrun() ????????{ ????????????System.out.println("普通線程池中的線程"); ????????} ????}); ????ScheduledThreadPoolExecutor pool3 = MyPool.getInstance() ????????????.getScheduledPool(); ????pool3.scheduleAtFixedRate(newRunnable() ????{ ????????@Override ????????publicvoidrun() ????????{ ????????????System.out.println("計劃任務線程池中的線程"); ????????} ????},?0,?1000, TimeUnit.MILLISECONDS); } |
|
?
四、計劃任務運行使用
????通過上面的樣例。也看到了計劃任務線程池的使用方式。對于計劃任務,除了能夠運行普不同線程池中線程的任務以外。還能夠運行計劃任務特殊的線程要求。比方:scheduleWithFixedDelay(command, initialDelay, delay, unit);在初始化延遲之后,以特定的延遲時間反復運行。scheduleAtFixedRate(command, initialDelay, period, unit);在初始化延遲時間之后,以固定頻率反復運行。這兩種的差別是下一次運行時間延遲計算的開始時間不同,第一種是從上一次任務開始運行的時候計算。另外一種是從上一次任務運行結束的時候計算。這兩種和java之前版本號中Timer類非常相似。
可是Timer有非常多缺陷,具體的缺陷不再具體說明。而這些缺陷都能夠通過ScheduledExecutorService給完美解決。所以是時候該丟棄Timer了。
?
?
?
總結
以上是生活随笔為你收集整理的java线程和线程池的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。