通过java.util.concurrent写多线程程序
生活随笔
收集整理的這篇文章主要介紹了
通过java.util.concurrent写多线程程序
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
在JDK 1.5之前,要實現多線程的功能,得用到Thread這個類,通過這個類設計多線程程序,需要考慮性能,死鎖,資源等很多因素,一句話,就是相當麻煩,而且很容易出問題。所幸的是,在JDK1.5之后,java.util.concurrent包出現了,這是一個設計良好的多線程工具類,本文就將介紹該類的基本使用方法。 按照本博的風格,依然是先扔上一段示例代碼,然后我們再慢慢講解 package com.giantray;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TestConcurrent {public static ExecutorService exec = Executors.newCachedThreadPool();public void startMutilThread(){int taskNum = 2;final CountDownLatch cd = new CountDownLatch(taskNum );//任務1exec.submit(new Runnable(){@Overridepublic void run(){System. out.println("1 start" );try {Thread. sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}cd.countDown();System. out.println("1 end" );}});//任務2exec.submit(new Runnable(){@Overridepublic void run(){System. out.println("2 start" );cd.countDown();System. out.println("2 end" );}});System. out.println("submit end" );try{cd.await();}catch (InterruptedException e){}System. out.print("end" );}public static void main(String[] args) {TestConcurrent testThread = new TestConcurrent();testThread.startMutilThread();}
}
輸出 1 start submit end 2 start 2 end 1 end end
下面進行詳細介紹:
2、final CountDownLatch cd = new CountDownLatch(taskNum ); 定義計數器,指定要并發執行的任務數
3、 exec .submit( new Runnable(){};定義任務的執行邏輯
4、cd.countDown();任務結束了,要讓計算器減1
5、cd.await();表示等待計數器小于等于0時,再繼續往下走其他邏輯
1、newFixedThreadPool(固定大小線程池)
線程數是固定的,例如線程數設為1,則需要等待任務1結束后,才執行任務2;如設為2,則兩個任務能同時執行
2、newCachedThreadPool(無限制大小的線程池)
線程數無固定大小,當前無可用線程,就回創建新線程。如果任務1沒結束,那么啟動任務2時,就會新建線程,這時線程池大小為2;但假如啟動任務2時,任務1已結束,則不會新建線程,會沿用任務1使用的線程
3、newSingleThreadExecutor(單線程)
使用單線程池時,就沒辦法同時執行多個任務,因此,本例中的任務1執行完后,才會執行任務2
4、newScheduledThreadPool(定時任務線程池)
支持定時任務的線程池,與Timer,Quartz一樣,常見于一些定時任務調度程序·
shutdown() 關閉線程池,但之前提交的任務,會繼續執行;如果新提交任務,會拋異常
shutdownNow() 馬上關閉線程池,之前提交的任務,如果還未執行完,會被終止,且拋出異常
對示例代碼做下修改,其中,task1.get()將拿到call()方法的return值,也就是示例中的"false"
int taskNum = 1; final CountDownLatch cd = new CountDownLatch(taskNum);Future<Boolean> task1 = exec.submit(new Callable<Boolean>(){@Overridepublic Boolean call() throws Exception {System.out.println("1 start");cd.countDown();System.out.println("1 end");return false;}});try {cd.await();Boolean result = task1.get();System.out.println("result : " + result); } catch (InterruptedException e) {e.printStackTrace(); } catch (ExecutionException e) {e.printStackTrace(); } System.out.print("end");
輸出 1 start submit end 2 start 2 end 1 end end
下面進行詳細介紹:
一、使用流程
1、ExecutorService exec = ?Executors.newCachedThreadPool();獲得一個線程池2、final CountDownLatch cd = new CountDownLatch(taskNum ); 定義計數器,指定要并發執行的任務數
3、 exec .submit( new Runnable(){};定義任務的執行邏輯
4、cd.countDown();任務結束了,要讓計算器減1
5、cd.await();表示等待計數器小于等于0時,再繼續往下走其他邏輯
二、線程池的管理
除了示例中的newCachedThreadPool(),也有其他各種類型的線程池:1、newFixedThreadPool(固定大小線程池)
線程數是固定的,例如線程數設為1,則需要等待任務1結束后,才執行任務2;如設為2,則兩個任務能同時執行
2、newCachedThreadPool(無限制大小的線程池)
線程數無固定大小,當前無可用線程,就回創建新線程。如果任務1沒結束,那么啟動任務2時,就會新建線程,這時線程池大小為2;但假如啟動任務2時,任務1已結束,則不會新建線程,會沿用任務1使用的線程
3、newSingleThreadExecutor(單線程)
使用單線程池時,就沒辦法同時執行多個任務,因此,本例中的任務1執行完后,才會執行任務2
4、newScheduledThreadPool(定時任務線程池)
支持定時任務的線程池,與Timer,Quartz一樣,常見于一些定時任務調度程序·
三、計數器CountDownLatch
1、示例講解
在示例中,我們通過多線程執行了兩個任務。之后我們需要知道,兩個任務是否都執行結束?都執行結束了,我們才能繼續執行接下來的其他邏輯。為了這個目的,需要引入CountDownLatch這個計數器。先聲明一個計算量為TaskNum(2)的計數器,接著,每個任務執行結束時,要執行countDown()方法,而最后的await()方法表示,如果CountDownLatch不為零,那么就在這里等待,下面的邏輯會被阻塞住,不會繼續執行,等待兩個任務都執行結束了--執行兩次countDown()方法,計數器等于0,這時候程序才會繼續往下執行System. out.print("end")這行代碼2、它的兄弟CyclicBarrier
還有一個類似CountDownLatch的計數器,名為CyclicBarrier。這兩者的區別:CountDownLatch被減至0后,是不能重置的。而CyclicBarrier被減至0后,會自動恢復至初始值,因此它是一個循環的計數器,舉一個例子,運動員跑步,這是一個任務,而這個任務,又分為運動員準備,以及開跑兩個子任務,所有的運動員,必須等待其他運動員都”準備“好后,才能起跑。為了實現這個邏輯,及可以用到CyclicBarrier,CyclicBarrier的計數初始值為運動員數量,運動員準備完畢時,讓計數器減1,這時候它不會進行”開跑“,等到計數器減為0時,所有運動員才同時“起跑”,并且會重新恢復至初始值,等到所有運動員都跑完時,計數器才又恢復為0,這時候,才能再繼續執行其他任務。而如果使用CyclicBarrier,在線程執行過程中,是不能等待其他線程的,不能等其他線程都“準備”好了,所有線程再一起“開跑”四、并發執行
多運行幾次示例程序,會發現,有時候,"2 start"會早于"1 start"輸出,因此,不一定是先submit的任務,就先“開始執行”。這也告訴我們,不能想當然地以為,任務1先submit,就可以在任務1里做一些全局初始化的工作,然后在任務2里可以去拿任務1初始化的變量。五、線程池的生命周期管理
1、三種基本狀態
線程池的聲明周期有三個狀態,運行,關閉,終止。初始化線程時,處于運行狀態,執行shutdown()方法后,處于關閉狀態,但這時候線程任務會繼續執行,當所有任務都結束后,才會變成終止狀態。2、shutdown()與shutdownNow()的區別
這兩個方法都用于關閉線程池shutdown() 關閉線程池,但之前提交的任務,會繼續執行;如果新提交任務,會拋異常
shutdownNow() 馬上關閉線程池,之前提交的任務,如果還未執行完,會被終止,且拋出異常
3、shutdown()的作用
- ? 釋放線程池資源(其實不關閉線程池,會資源會有多大的消耗,筆者也不清楚)
- ? 可以讓程序有序退出。這是什么意思呢?講一個例子,你就明白了。假如你的程序正在并發執行任務,而且還一直會提交新的任務。這時候,該如何設計程序的“退出”功能呢?首先,為了任務的原子性,希望能執行完已提交的任務,并且阻止新的任務提交。這時候,就得用到了shutdown()了。退出功能的代碼可以是這樣:
六、獲取線程任務的執行結果
如果想知道任務的執行結果,就得使用Future和Callable。Future可以拿到結果,而Callable能返回值,用于產生結果對示例代碼做下修改,其中,task1.get()將拿到call()方法的return值,也就是示例中的"false"
int taskNum = 1; final CountDownLatch cd = new CountDownLatch(taskNum);Future<Boolean> task1 = exec.submit(new Callable<Boolean>(){@Overridepublic Boolean call() throws Exception {System.out.println("1 start");cd.countDown();System.out.println("1 end");return false;}});try {cd.await();Boolean result = task1.get();System.out.println("result : " + result); } catch (InterruptedException e) {e.printStackTrace(); } catch (ExecutionException e) {e.printStackTrace(); } System.out.print("end");
?
總結
以上是生活随笔為你收集整理的通过java.util.concurrent写多线程程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DHTML【7】--CSS
- 下一篇: InnoDB和MyISAM的区别与选择