天天看點

java多線程

1、程序與線程

     我們可以在計算機上運作各種計算機軟體程式。每一個運作的程式可能包括多個獨立運作的線程(Thread)。 

線程(Thread)是一份獨立運作的程式,有自己專用的運作棧。線程有可能和其他線程共享一些資源,比如,記憶體,檔案,資料庫等。 

當多個線程同時讀寫同一份共享資源的時候,可能會引起沖突。這時候,我們需要引入線程“同步”機制,即各位線程之間要有個先來後到,不能一窩蜂擠上去搶作一團。 

同步這個詞是從英文synchronize(使同時發生)翻譯過來的。我也不明白為什麼要用這個很容易引起誤解的詞。既然大家都這麼用,咱們也就隻好這麼将就。 

線程同步的真實意思和字面意思恰好相反。線程同步的真實意思,其實是“排隊”:幾個線程之間要排隊,一個一個對共享資源進行操作,而不是同時進行操作。 

是以,關于線程同步,需要牢牢記住的第一點是:線程同步就是線程排隊。同步就是排隊。線程同步的目的就是避免線程“同步”執行。這可真是個無聊的繞密碼。 

關于線程同步,需要牢牢記住的第二點是 “共享”這兩個字。隻有共享資源的讀寫通路才需要同步。如果不是共享資源,那麼就根本沒有同步的必要。 

關于線程同步,需要牢牢記住的第三點是,隻有“變量”才需要同步通路。如果共享的資源是固定不變的,那麼就相當于“常量”,線程同時讀取常量也不需要同步。至少一個線程修改共享資源,這樣的情況下,線程之間就需要同步。 

關于線程同步,需要牢牢記住的第四點是:多個線程通路共享資源的代碼有可能是同一份代碼,也有可能是不同的代碼;無論是否執行同一份代碼,隻要這些線程的代碼通路同一份可變的共享資源,這些線程之間就需要同步。 

為了加深了解,下面舉幾個例子。 

有兩個采購員,他們的工作内容是相同的,都是遵循如下的步驟: 

(1)到市場上去,尋找并購買有潛力的樣品。 

(2)回到公司,寫報告。 

這兩個人的工作内容雖然一樣,他們都需要購買樣品,他們可能買到同樣種類的樣品,但是他們絕對不會購買到同一件樣品,他們之間沒有任何共享資源。是以,他們可以各自進行自己的工作,互不幹擾。 

這兩個采購員就相當于兩個線程;兩個采購員遵循相同的工作步驟,相當于這兩個線程執行同一段代碼。 

下面給這兩個采購員增加一個工作步驟。采購員需要根據公司的“布告欄”上面公布的資訊,安排自己的工作計劃。 

這兩個采購員有可能同時走到布告欄的前面,同時觀看布告欄上的資訊。這一點問題都沒有。因為布告欄是隻讀的,這兩個采購員誰都不會去修改布告欄上寫的資訊。 

下面增加一個角色。一個辦公室行政人員這個時候,也走到了布告欄前面,準備修改布告欄上的資訊。 

如果行政人員先到達布告欄,并且正在修改布告欄的内容。兩個采購員這個時候,恰好也到了。這兩個采購員就必須等待行政人員完成修改之後,才能觀看修改後的資訊。 

如果行政人員到達的時候,兩個采購員已經在觀看布告欄了。那麼行政人員需要等待兩個采購員把目前資訊記錄下來之後,才能夠寫上新的資訊。 

上述這兩種情況,行政人員和采購員對布告欄的通路就需要進行同步。因為其中一個線程(行政人員)修改了共享資源(布告欄)。而且我們可以看到,行政人員的工作流程和采購員的工作流程(執行代碼)完全不同,但是由于他們通路了同一份可變共享資源(布告欄),是以他們之間需要同步。 

同步鎖 

前面講了為什麼要線程同步,下面我們就來看如何才能線程同步。 

線程同步的基本實作思路還是比較容易了解的。我們可以給共享資源加一把鎖,這把鎖隻有一把鑰匙。哪個線程擷取了這把鑰匙,才有權利通路該共享資源。 

生活中,我們也可能會遇到這樣的例子。一些超市的外面提供了一些自動儲物箱。每個儲物箱都有一把鎖,一把鑰匙。人們可以使用那些帶有鑰匙的儲物箱,把東西放到儲物箱裡面,把儲物箱鎖上,然後把鑰匙拿走。這樣,該儲物箱就被鎖住了,其他人不能再通路這個儲物箱。(當然,真實的儲物箱鑰匙是可以被人拿走複制的,是以不要把貴重物品放在超市的儲物箱裡面。于是很多超市都采用了電子密碼鎖。) 

線程同步鎖這個模型看起來很直覺。但是,還有一個嚴峻的問題沒有解決,這個同步鎖應該加在哪裡? 

當然是加在共享資源上了。反應快的讀者一定會搶先回答。 

沒錯,如果可能,我們當然盡量把同步鎖加在共享資源上。一些比較完善的共享資源,比如,檔案系統,資料庫系統等,自身都提供了比較完善的同步鎖機制。我們不用另外給這些資源加鎖,這些資源自己就有鎖。 

但是,大部分情況下,我們在代碼中通路的共享資源都是比較簡單的共享對象。這些對象裡面沒有地方讓我們加鎖。 

讀者可能會提出建議:為什麼不在每一個對象内部都增加一個新的區域,專門用來加鎖呢?這種設計理論上當然也是可行的。問題在于,線程同步的情況并不是很普遍。如果因為這小機率事件,在所有對象内部都開辟一塊鎖空間,将會帶來極大的空間浪費。得不償失。 

于是,現代的程式設計語言的設計思路都是把同步鎖加在代碼段上。确切的說,是把同步鎖加在“通路共享資源的代碼段”上。這一點一定要記住,同步鎖是加在代碼段上的。 

同步鎖加在代碼段上,就很好地解決了上述的空間浪費問題。但是卻增加了模型的複雜度,也增加了我們的了解難度。 

現在我們就來仔細分析“同步鎖加在代碼段上”的線程同步模型。 

首先,我們已經解決了同步鎖加在哪裡的問題。我們已經确定,同步鎖不是加在共享資源上,而是加在通路共享資源的代碼段上。 

其次,我們要解決的問題是,我們應該在代碼段上加什麼樣的鎖。這個問題是重點中的重點。這是我們尤其要注意的問題:通路同一份共享資源的不同代碼段,應該加上同一個同步鎖;如果加的是不同的同步鎖,那麼根本就起不到同步的作用,沒有任何意義。 

這就是說,同步鎖本身也一定是多個線程之間的共享對象。 

Java語言的synchronized關鍵字 

為了加深了解,舉幾個代碼段同步的例子。 

不同語言的同步鎖模型都是一樣的。隻是表達方式有些不同。這裡我們以目前最流行的Java語言為例。Java語言裡面用synchronized關鍵字給代碼段加鎖。整個文法形式表現為

這裡尤其要注意的就是,同步鎖本身一定要是共享的對象。 

上面這段代碼沒有任何意義。因為那個同步鎖是在函數體内部産生的。每個線程調用這段代碼的時候,都會産生一個新的同步鎖。那麼多個線程之間,使用的是不同的同步鎖。根本達不到同步的目的。 

同步代碼一定要寫成如下的形式,才有意義。 

你不一定要把同步鎖聲明為static或者public,但是你一定要保證相關的同步代碼之間,一定要使用同一個同步鎖。

講到這裡,你一定會好奇,這個同步鎖到底是個什麼東西。為什麼随便聲明一個Object對象,就可以作為同步鎖?

在Java裡面,同步鎖的概念就是這樣的。任何一個Object Reference都可以作為同步鎖。我們可以把Object Reference了解為對象在記憶體配置設定系統中的記憶體位址。是以,要保證同步代碼段之間使用的是同一個同步鎖,我們就要保證這些同步代碼段的synchronized關鍵字使用的是同一個Object Reference,同一個記憶體位址。這也是為什麼我在前面的代碼中聲明lock1的時候,使用了final關鍵字,這就是為了保證lock1的Object Reference在整個系統運作過程中都保持不變。 

一些求知欲強的讀者可能想要繼續深入了解synchronzied(同步鎖)的實際運作機制。Java虛拟機規範中(你可以在google用“JVM Spec”等關鍵字進行搜尋),有對synchronized關鍵字的詳細解釋。synchronized會編譯成 monitor enter, … monitor exit之類的指令對。Monitor就是實際上的同步鎖。每一個Object Reference在概念上都對應一個monitor。 

這些實作細節問題,并不是了解同步鎖模型的關鍵。我們繼續看幾個例子,加深對同步鎖模型的了解。 

上述的代碼中,代碼段A和代碼段B就是同步的。因為它們使用的是同一個同步鎖lock1。 

如果有10個線程同時執行代碼段A,同時還有20個線程同時執行代碼段B,那麼這30個線程之間都是要進行同步的。 

這30個線程都要競争一個同步鎖lock1。同一時刻,隻有一個線程能夠獲得lock1的所有權,隻有一個線程可以執行代碼段A或者代碼段B。其他競争失敗的線程隻能暫停運作,進入到該同步鎖的就緒(Ready)隊列。 

每一個同步鎖下面都挂了幾個線程隊列,包括就緒(Ready)隊列,待召(Waiting)隊列等。比如,lock1對應的就緒隊列就可以叫做lock1 - ready queue。每個隊列裡面都可能有多個暫停運作的線程。 

注意,競争同步鎖失敗的線程進入的是該同步鎖的就緒(Ready)隊列,而不是後面要講述的待召隊列(Waiting Queue,也可以翻譯為等待隊列)。就緒隊列裡面的線程總是時刻準備着競争同步鎖,時刻準備着運作。而待召隊列裡面的線程則隻能一直等待,直到等到某個信号的通知之後,才能夠轉移到就緒隊列中,準備運作。 

成功擷取同步鎖的線程,執行完同步代碼段之後,會釋放同步鎖。該同步鎖的就緒隊列中的其他線程就繼續下一輪同步鎖的競争。成功者就可以繼續運作,失敗者還是要乖乖地待在就緒隊列中。 

是以,線程同步是非常耗費資源的一種操作。我們要盡量控制線程同步的代碼段範圍。同步的代碼段範圍越小越好。我們用一個名詞“同步粒度”來表示同步代碼段的範圍。 

同步粒度 

在Java語言裡面,我們可以直接把synchronized關鍵字直接加在函數的定義上。 

比如。 

這段代碼就等價于 

但是,我們要盡量避免這種直接把synchronized加在函數定義上的偷懶做法。因為我們要控制同步粒度。同步的代碼段越小越好。synchronized控制的範圍越小越好。 

我們不僅要在縮小同步代碼段的長度上下功夫,我們同時還要注意細分同步鎖。 

比如,下面的代碼 

上述的4段同步代碼,使用同一個同步鎖lock1。所有調用4段代碼中任何一段代碼的線程,都需要競争同一個同步鎖lock1。 

我們仔細分析一下,發現這是沒有必要的。 

因為f1()的代碼段A和f2()的代碼段B通路的共享資源是resource1,f3()的代碼段C和f4()的代碼段D通路的共享資源是resource2,它們沒有必要都競争同一個同步鎖lock1。我們可以增加一個同步鎖lock2。f3()和f4()的代碼可以修改為: 

這樣,f1()和f2()就會競争lock1,而f3()和f4()就會競争lock2。這樣,分開來分别競争兩個鎖,就可以大大較少同步鎖競争的機率,進而減少系統的開銷。 

2、建立線程

3、線程狀态:

NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED。

1、NEW(新生):當剛建立一個程序,并未執行start的時候,改程序處于NEW狀态。

2、RUNNABLE(可運作):當一個程序調用start方法,就進入了RUNNABLE狀态。處于可運作狀态程序不一定是正在運作的,是否運作取決于系統配置設定給他的運作時間。

3、BLOCKED(被阻塞):當一個線程去申請内部對象鎖,但是這個鎖被其他線程所占有,此時進入被阻塞狀态。

4、WAITING(等待):當一個線程等待另一個線程通知排程器一個條件是時。

5、TIMED_WAITING(記時等待):有些方法會傳遞逾時參數,調用它們使得線程進入計時等待。

6、TERMINATED(終止):一個是run方法結束,自然死亡。另一個是捕獲異常導緻線程終端。

API:

java.lang.Thread

thread.State getState():得到線程狀态。

3、程序排程

  有兩種排程模型:分時排程模型和搶占式排程模型。

  分時排程模型是指讓所有的線程輪流獲得cpu的使用權,并且平均配置設定每個線程占用的CPU的時間片這個也比較好了解。

  java虛拟機采用搶占式排程模型,是指優先讓可運作池中優先級高的線程占用CPU,如果可運作池中的線程優先級相同,那麼就随機選擇一個線程,使其占用CPU。處于運作狀态的線程會一直運作,直至它不得不放棄CPU。

        是以,java想要明确程序運作狀态就需要用到一下方法:

  讓處于運作狀态的線程調用Thread.sleep()方法 (給予低級線程運作機會)

  讓處于運作狀态的線程調用Thread.yield()方法 (給予同等優先級的線程運作機會)

  讓處于運作狀态的線程調用另一個線程的join()方法(迫使程序處于阻塞狀态)

調整各個線程的優先級 

Thread類的setPriority(int)和getPriority()方法分别用來設定優先級和讀取優先級。 

如果希望程式能夠移值到各個作業系統中,應該確定在設定線程的優先級時,隻使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY這3個優先級。 

線程睡眠:當線程在運作中執行了sleep()方法時,它就會放棄CPU,轉到阻塞狀态。 

線程讓步:當線程在運作中執行了Thread類的yield()靜态方法時,如果此時具有相同優先級的其它線程處于就緒狀态,那麼yield()方法将把目前運作的線程放到運作池中并使另一個線程運作。如果沒有相同優先級的可運作線程,則yield()方法什麼也不做。 

Sleep()方法和yield()方法都是Thread類的靜态方法,都會使目前處于運作狀态的線程放棄CPU,把運作機會讓給别的線程,兩者的差別在于: 

         1、sleep()方法會給其他線程運作的機會,而不考慮其他線程的優先級,是以會給較低線程一個運作的機會;yield()方法隻會給相同優先級或者更高優先級的線程一個運作的機會。 

     2、當線程執行了sleep(long millis)方法後,将轉到阻塞狀态,參數millis指定睡眠時間;當線程執行了yield()方法後,将轉到就緒狀态。 

         3、sleep()方法聲明抛出InterruptedException異常,而yield()方法沒有聲明抛出任何異常 

         4、sleep()方法比yield()方法具有更好的移植性 

等待其它線程的結束:join() 

          目前運作的線程可以調用另一個線程的 join()方法,目前運作的線程将轉到阻塞狀态,直到另一個線程運作結束,它才恢複運作。 

定時器Timer:在JDK的java.util包中提供了一個實用類Timer, 它能夠定時執行特定的任務。 

線程的同步 

原子操作:根據Java規範,對于基本類型的指派或者傳回值操作,是原子操作。但這裡的基本資料類型不包括long和double, 因為JVM看到的基本存儲機關是32位,而long 和double都要用64位來表示。是以無法在一個時鐘周期内完成。 

自增操作(++)不是原子操作,因為它涉及到一次讀和一次寫。 

原子操作:由一組相關的操作完成,這些操作可能會操縱與其它的線程共享的資源,為了保證得到正确的運算結果,一個線程在執行原子操作其間,應該采取其他的措施使得其他的線程不能操縱共享資源。 

同步代碼塊:為了保證每個線程能夠正常執行原子操作,Java引入了同步機制,具體的做法是在代表原子操作的程式代碼前加上synchronized标記,這樣的代碼被稱為同步代碼塊。 

同步鎖:每個JAVA對象都有且隻有一個同步鎖,在任何時刻,最多隻允許一個線程擁有這把鎖。 

當一個線程試圖通路帶有synchronized(this)标記的代碼塊時,必須獲得 this關鍵字引用的對象的鎖,在以下的兩種情況下,本線程有着不同的命運。 

1、 假如這個鎖已經被其它的線程占用,JVM就會把這個線程放到本對象的鎖池中。本線程進入阻塞狀态。鎖池中可能有很多的線程,等到其他的線程釋放了鎖,JVM就會從鎖池中随機取出一個線程,使這個線程擁有鎖,并且轉到就緒狀态。 

2、 假如這個鎖沒有被其他線程占用,本線程會獲得這把鎖,開始執行同步代碼塊。 

(一般情況下在執行同步代碼塊時不會釋放同步鎖,但也有特殊情況會釋放對象鎖 

如在執行同步代碼塊時,遇到異常而導緻線程終止,鎖會被釋放;在執行代碼塊時,執行了鎖所屬對象的wait()方法,這個線程會釋放對象鎖,進入對象的等待池中) 

線程同步的特征: 

1、 如果一個同步代碼塊和非同步代碼塊同時操作共享資源,仍然會造成對共享資源的競争。因為當一個線程執行一個對象的同步代碼塊時,其他的線程仍然可以執行對象的非同步代碼塊。(所謂的線程之間保持同步,是指不同的線程在執行同一個對象的同步代碼塊時,因為要獲得對象的同步鎖而互相牽制) 

2、 每個對象都有唯一的同步鎖 

3、 在靜态方法前面可以使用synchronized修飾符。 

4、 當一個線程開始執行同步代碼塊時,并不意味着必須以不間斷的方式運作,進入同步代碼塊的線程可以執行Thread.sleep()或者執行Thread.yield()方法,此時它并不釋放對象鎖,隻是把運作的機會讓給其他的線程。 

5、 Synchronized聲明不會被繼承,如果一個用synchronized修飾的方法被子類覆寫,那麼子類中這個方法不在保持同步,除非用synchronized修飾。 

線程安全的類: 

1、 這個類的對象可以同時被多個線程安全的通路。 

2、 每個線程都能正常的執行原子操作,得到正确的結果。 

3、 在每個線程的原子操作都完成後,對象處于邏輯上合理的狀态。 

釋放對象的鎖: 

1、 執行完同步代碼塊就會釋放對象的鎖 

2、 在執行同步代碼塊的過程中,遇到異常而導緻線程終止,鎖也會被釋放 

3、 在執行同步代碼塊的過程中,執行了鎖所屬對象的wait()方法,這個線程會釋放對象鎖,進入對象的等待池。 

死鎖 

當一個線程等待由另一個線程持有的鎖,而後者正在等待已被第一個線程持有的鎖時,就會發生死鎖。JVM不監測也不試圖避免這種情況,是以保證不發生死鎖就成了程式員的責任。 

如何避免死鎖 

一個通用的經驗法則是:當幾個線程都要通路共享資源A、B、C 時,保證每個線程都按照同樣的順序去通路他們。 

線程通信 

Java.lang.Object類中提供了兩個用于線程通信的方法 

1、 wait():執行了該方法的線程釋放對象的鎖,JVM會把該線程放到對象的等待池中。該線程等待其它線程喚醒 

2、 notify():執行該方法的線程喚醒在對象的等待池中等待的一個線程,JVM從對象的等待池中随機選擇一個線程,把它轉到對象的鎖池中。

信号量 

同步鎖模型隻是最簡單的同步模型。同一時刻,隻有一個線程能夠運作同步代碼。 

有的時候,我們希望處理更加複雜的同步模型,比如生産者/消費者模型、讀寫同步模型等。這種情況下,同步鎖模型就不夠用了。我們需要一個新的模型。這就是我們要講述的信号量模型。 

信号量模型的工作方式如下:線程在運作的過程中,可以主動停下來,等待某個信号量的通知;這時候,該線程就進入到該信号量的待召(Waiting)隊列當中;等到通知之後,再繼續運作。 

很多語言裡面,同步鎖都由專門的對象表示,對象名通常叫Monitor。 

同樣,在很多語言中,信号量通常也有專門的對象名來表示,比如,Mutex,Semphore。 

信号量模型要比同步鎖模型複雜許多。一些系統中,信号量甚至可以跨程序進行同步。另外一些信号量甚至還有計數功能,能夠控制同時運作的線程數。 

我們沒有必要考慮那麼複雜的模型。所有那些複雜的模型,都是最基本的模型衍生出來的。隻要掌握了最基本的信号量模型——“等待/通知”模型,複雜模型也就迎刃而解了。 

我們還是以Java語言為例。Java語言裡面的同步鎖和信号量概念都非常模糊,沒有專門的對象名詞來表示同步鎖和信号量,隻有兩個同步鎖相關的關鍵字——volatile和synchronized。 

這種模糊雖然導緻概念不清,但同時也避免了Monitor、Mutex、Semphore等名詞帶來的種種誤解。我們不必執着于名詞之争,可以專注于了解實際的運作原理。 

在Java語言裡面,任何一個Object Reference都可以作為同步鎖。同樣的道理,任何一個Object Reference也可以作為信号量。 

Object對象的wait()方法就是等待通知,Object對象的notify()方法就是發出通知。 

具體調用方法為 

(1)等待某個信号量的通知 

public static final Object signal = new Object(); 

… f1() { 

synchronized(singal) { // 首先我們要擷取這個信号量。這個信号量同時也是一個同步鎖 

// 隻有成功擷取了signal這個信号量兼同步鎖之後,我們才可能進入這段代碼 

signal.wait(); // 這裡要放棄信号量。本線程要進入signal信号量的待召(Waiting)隊列 

// 可憐。辛辛苦苦争取到手的信号量,就這麼被放棄了 

// 等到通知之後,從待召(Waiting)隊列轉到就緒(Ready)隊列裡面 

// 轉到了就緒隊列中,離CPU核心近了一步,就有機會繼續執行下面的代碼了。 

// 仍然需要把signal同步鎖競争到手,才能夠真正繼續執行下面的代碼。命苦啊。 

… 

需要注意的是,上述代碼中的signal.wait()的意思。signal.wait()很容易導緻誤解。signal.wait()的意思并不是說,signal開始wait,而是說,運作這段代碼的目前線程開始wait這個signal對象,即進入signal對象的待召(Waiting)隊列。 

(2)發出某個信号量的通知 

… f2() { 

synchronized(singal) { // 首先,我們同樣要擷取這個信号量。同時也是一個同步鎖。 

signal.notify(); // 這裡,我們通知signal的待召隊列中的某個線程。 

// 如果某個線程等到了這個通知,那個線程就會轉到就緒隊列中 

// 但是本線程仍然繼續擁有signal這個同步鎖,本線程仍然繼續執行 

// 嘿嘿,雖然本線程好心通知其他線程, 

// 但是,本線程可沒有那麼高風亮節,放棄到手的同步鎖 

// 本線程繼續執行下面的代碼 

一般的情況是signal.notify()是此段代碼的最後一條語句 

需要注意的是,signal.notify()的意思。signal.notify()并不是通知signal這個對象本身。而是通知正在等待signal信号量的其他線程。 

以上就是Object的wait()和notify()的基本用法。 

實際上,wait()還可以定義等待時間,當線程在某信号量的待召隊列中,等到足夠長的時間,就會等無可等,無需再等,自己就從待召隊列轉移到就緒隊列中了。 

另外,還有一個notifyAll()方法,表示通知待召隊列裡面的所有線程。 

這些細節問題,并不對大局産生影響。

綠色線程 

綠色線程(Green Thread)是一個相對于作業系統線程(Native Thread)的概念。 

作業系統線程(Native Thread)的意思就是,程式裡面的線程會真正映射到作業系統的線程(核心級線程),線程的運作和排程都是由作業系統控制的 

綠色線程(Green Thread)的意思是,程式裡面的線程不會真正映射到作業系統的線程,而是由語言運作平台自身來排程。 

目前版本的Python語言的線程就可以映射到作業系統線程。目前版本的Ruby語言的線程就屬于綠色線程,無法映射到作業系統的線程,是以Ruby語言的線程的運作速度比較慢。 

難道說,綠色線程要比作業系統線程要慢嗎?當然不是這樣。事實上,情況可能正好相反。Ruby是一個特殊的例子。線程排程器并不是很成熟。 

目前,線程的流行實作模型就是綠色線程。比如,stackless Python,就引入了更加輕量的綠色線程概念。線上程并發程式設計方面,無論是運作速度還是并發負載上,都優于Python。 

另一個更著名的例子就是ErLang(愛立信公司開發的一種開源語言)。 

ErLang的綠色線程概念非常徹底。ErLang的線程不叫Thread,而是叫做Process。這很容易和程序混淆起來。這裡要注意區分一下。 

ErLang Process之間根本就不需要同步。因為ErLang語言的所有變量都是final的,不允許變量的值發生任何變化。是以根本就不需要同步。 

final變量的另一個好處就是,對象之間不可能出現交叉引用,不可能構成一種環狀的關聯,對象之間的關聯都是單向的,樹狀的。是以,記憶體垃圾回收的算法效率也非常高。這就讓ErLang能夠達到Soft Real Time(軟實時)的效果。這對于一門支援記憶體垃圾回收的語言來說,可不是一件容易的事情

上一篇: Java 多線程
下一篇: java多線程

繼續閱讀