併發程式設計基礎(篇一)
併發程式設計——基礎部分(篇一)
這幾天在研究Java中的執行緒機制,結果越是看,反而感覺越是掌握的太少,變得多疑而又自信,因為Java執行緒機制教會我,“理論上是可靠的,實際是不準確的。”,一直向前走,越會發現精彩而又迷惑的地方,這就是Java中的執行緒機制這幾天所帶給我的一些東西,雖然只是研究了很小一部分,但是感覺也很有收穫,所以在這裡將自己的讀書筆記或是可以稱為收貨記錄下來。
凡事都可以問一個為什麼,那麼我們也可以在這裡一路為什麼下去。
- 為什麼要使用併發?
當第一次多執行緒這個概念進入我們腦海的時候,我們沒有問過為什麼會存在這個,也沒有想過,但是我們現在需要考慮這個問題,這是為什麼呢?多執行緒的效果是什麼,是可以同時讓多個任務同時執行,那麼我們自然可以想到,比起所有的任務順序執行,同時執行多個任務會更節省時間。但是如果我的機器只有一個處理器,還會節省時間嗎?答案是否定的,因為我們知道處理器一次只能執行一條指令,那麼如果是多執行緒的話,為了達到效果,那麼在處理器上肯定會存線上程之間的切換,那麼這與所有的任務順序執行相比,是變得慢了,而不是更快。但是為什麼我們還選擇多執行緒呢?因為存在阻塞!這才是問題的核心,對於我們使用的使用者介面,如果不使用多執行緒,那麼怎麼可能事實響應使用者的輸入,而且又在做著處理別的事情的工作呢,因此這才是多執行緒出現比較重要的原因,而且如果我們企圖通過在順序執行中去實時檢測使用者輸入,或是什麼,那麼我不敢想我們會將程式寫的有多麼複雜。
- 為什麼是執行緒而不是程序?
實現併發的最直接的方式是在作業系統級別使用程序,程序是執行在自己的地址空間的自包容程式。多工作業系統可以週期性地將CPU從一個程序切換到另一個程序,來實現同時執行多個程式。作業系統會將程序之間進行隔離,它們之間互不干涉,這使得程序程式設計相對容易一些。與此相反,Java所使用的這種併發系統會共享諸如記憶體和I/O這樣的資源,因此編寫多執行緒程式最基本的困難在於協調不同執行緒驅動的任務之間對這些資源的使用,以使得這些資源不會同時被多個任務訪問。程序之間雖然隔離,是併發的理想模型,但是程序通常會有數量和開銷的限制,以避免它們在不同的併發系統之間的可用性。Java中的執行緒機制是在由執行程式表示的單一程序中建立任務。
- 來一點執行緒的乾貨
Java的執行緒機制是搶佔式的,因此我們永遠不知道下一次執行的會是誰?這表示排程機制會週期性地中斷執行緒,切換到另一個執行緒,從而為每個執行緒都提供時間片,使得每個執行緒都會分配到數量合理的時間去驅動任務。但是在協作式系統中,每個任務都會自動地放棄控制,這要求在程式語句中插入某種讓步語句。協作式系統的優勢是雙重的:上下文切換的開銷通常比搶佔式系統要低廉許多,並且可以同時執行的執行緒數量在理論上是沒有限制的。併發程式設計使我們可以將程式劃分為多個分離的、獨立執行的任務。通過使用多執行緒機制,這些獨立任務中的每一個都將由執行執行緒來驅動。一個執行緒就是在程序中的一個單一的順序控制流,因此單個程序可以擁有多個併發執行的任務,但是程式使得每個任務都好像有其自己的CPU
- 如何建立多工程式?
1、實現Runnable介面
首先我們需要來描述任務,這可以由實現Runnable介面來提供,只需要實現Runnable介面,編寫裡面的run()方法將你的任務新增進去即可,然後顯式將任務附著到一個執行緒上,就是將Runnable物件交給Thread構造器。然後啟動Thread物件呼叫start()方法,這樣就可以與其它執行緒“同時”執行了。如下:
Java程式碼- class MTask implements Runnable{
- /**
- * 實現接口裡面的run()方法
- */
- publicvoid run() {
- System.out.println("為人民服務!");
- }
- }
- publicclass MyTask {
- publicstaticvoid main(String[] args){
- Thread thread = new Thread(new MTask());
- thread.start();
- }
- }
上面首先類MTask實現介面Runnable,在介面方法run()裡面描述需要執行的任務(列印字串),然後在下面的類中,建立了一個執行緒物件,將任務附著到這個執行緒上,呼叫執行緒啟動的方法,就可以實現了。
2、繼承Thread類
繼承Thread,雖然我們可以通過繼承Thread類,來實現併發程式設計,但是由於Java中的單根繼承,因此當我們繼承了Thread類時就不能繼承其它的類,這有時會是一種限制。如下:
Java程式碼- publicclass MyTask extends Thread{
- publicstaticvoid main(String[] args){
- MyTask mt = new MyTask();
- mt.start();
- }
- /**
- * 重寫Thread中的run()方法
- */
- publicvoid run(){
- System.out.println("為人民服務!");
- }
- }
如上,可以直接繼承Thread類,在裡面重寫run()方法,來編寫需要實現的任務(仍是列印字串),然後建立該類的物件,執行。
我們可以發現,不管是實現介面Runnable還是繼承Thread,最後啟動都是呼叫start()方法,通過start()方法來啟動執行緒的根本原因是,執行緒總是由作業系統來佔有和管理,一個新的執行緒只能由作業系統來建立和啟動,如果自己直接呼叫run()方法,那麼這和其他方法有什麼區別呢,還是在呼叫的執行緒中執行。
- 使用Executor
Java.util.concurrent包中的執行器(Executor)將為我們管理Thread物件。從而簡化併發程式設計,Executor在客戶端和任務執行之間提供了一個間接層;與客戶端直接執行任務不同,這個中介物件將執行任務。ExecutorService物件是使用靜態的Executor方法建立的。使用靜態方法可以建立不同型別的Executor,有(下面僅列出三種)
CachedThreadPool(會在執行過程中建立與所需數量相同的執行緒,回收舊執行緒時停止建立新的執行緒);
FixedThreadPool(可以預先一次性執行代價高昂的執行緒分配);
SingleThreadExecutor(就像執行緒數量為1的FixedThreadPool,如果向它提交多工,那麼這些任務將排隊,每個任務都會在下一個任務開始前結束,所有任務使用相同的執行緒,它提供了一種重要的併發保證,這會改變任務的加鎖需求)
1、使用CachedThreadPool
Java程式碼- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- class Task implements Runnable{
- /**
- * 編寫需要執行的任務
- */
- publicvoid run() {
- System.out.println("為人民服務!");
- }
- }
- publicclass MyTask extends Thread{
- publicstaticvoid main(String[] args){
- ExecutorService ecs = Executors.newCachedThreadPool();
- ecs.execute(new Task());
- ecs.shutdown();
- }
- }
其中ExecutorService物件通過Executors類的靜態方法獲得,提供CachedThreadPool,然後我們只需要將建立的新任務交給ecs物件,呼叫execute()方法即可,那麼我們不需要顯式建立以及啟動執行緒,因此這就好比我們需要運輸東西,沒有必要自己再造個車(Thread)來進行運輸,只需要將運輸任務交給Executor就好,來負責管理Thread。其中後面呼叫shutdown()方法是為了防止繼續給ecs提交新的任務。FixedThreadPool和SingleThreadExecutor的使用和上面很類似。如下:
2、使用FixedThreadPool
使用FixedThreadPool預先一次性執行制定數目的執行緒分配,假如我們指定分配3個執行緒,提交多餘3個的任務,那麼只有3個執行緒在執行這些任務,示例如下:
Java程式碼- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.ThreadFactory;
- class Task implements Runnable{
- /**
- * 編寫需要執行的任務
- */
- publicvoid run() {
- System.out.println(Thread.currentThread().getName() + "為人民服務!");
- }
- }
- class M_ThreadFactory implements ThreadFactory{
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- System.out.println("建立了執行緒:" + t.getName());
- return t;
- }
- }
- publicclass MyTask extends Thread{
- publicstaticvoid main(String[] args){
- ExecutorService ecs = Executors.newFixedThreadPool(3, new M_ThreadFactory());
- for(int i = 0; i < 10; i++){
- ecs.execute(new Task());
- }
- ecs.shutdown();
- }
- }
在上面的程式中,忽視其中的類M_ThreadFactory,那個只是用來為了方便證明Executor中只有指定個數個執行緒(在函式中指定的是3),看一下輸出結果,就很明白,如下:
控制檯輸出: 建立了執行緒:Thread-0建立了執行緒:Thread-1
Thread-0為人民服務!
Thread-1為人民服務!
建立了執行緒:Thread-2
Thread-2為人民服務!
Thread-2為人民服務!
Thread-2為人民服務!
Thread-2為人民服務!
Thread-2為人民服務!
Thread-2為人民服務!
Thread-2為人民服務!
Thread-2為人民服務!
可以明顯看到,裡面只有Thread-0、Thread-1和Thread-2,這三個執行緒。
由於SingleThreadExecutor可以視為這個的指定執行緒數為1的特殊情況,因此這裡不進行演示。
現在時間不是很早了。。。。。。寫到這裡打住,前面這些只是併發方面的一些比較基礎的東西,後面還有很多。。。。。。嘿嘿
未完,待續!