自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

編寫多線程Java應(yīng)用程序常見(jiàn)問(wèn)題

開(kāi)發(fā) 后端
本文介紹的是多線程java應(yīng)用程序中常見(jiàn)的問(wèn)題,希望對(duì)你有幫助,一起來(lái)看吧!

幾乎JAVA中使用的所有 AWT 或 Swing 編寫的畫圖程序都需要多線程。但多線程程序會(huì)造成許多困難,剛開(kāi)始編程的開(kāi)發(fā)者常常會(huì)發(fā)現(xiàn)他們被一些問(wèn)題所折磨,例如不正確的程序行為或死鎖。

在本文中,我們將探討使用多線程時(shí)遇到的問(wèn)題,并提出那些常見(jiàn)陷阱的解決方案。

線程是什么?

一個(gè)程序或進(jìn)程能夠包含多個(gè)線程,這些線程可以根據(jù)程序的代碼執(zhí)行相應(yīng)的指令。多線程看上去似乎在并行執(zhí)行它們各自的工作,就像在一臺(tái)計(jì)算機(jī)上運(yùn)行著多個(gè)處理機(jī)一樣。在多處理機(jī)計(jì)算機(jī)上實(shí)現(xiàn)多線程時(shí),它們確實(shí)可以并行工作。和進(jìn)程不同的是,線程共享地址空間。也就是說(shuō),多個(gè)線程能夠讀寫相同的變量或數(shù)據(jù)結(jié)構(gòu)。

編寫多線程程序時(shí),你必須注意每個(gè)線程是否干擾了其他線程的工作??梢詫⒊绦蚩醋饕粋€(gè)辦公室,如果不需要共享辦公室資源或與其他人交流,所有職員就會(huì)獨(dú)立并行地工作。某個(gè)職員若要和其他人交談,當(dāng)且僅當(dāng)該職員在“聽(tīng)”且他們兩說(shuō)同樣的語(yǔ)言。此外,只有在復(fù)印機(jī)空閑且處于可用狀態(tài)(沒(méi)有僅完成一半的復(fù)印工作,沒(méi)有紙張阻塞等問(wèn)題)時(shí),職員才能夠使用它。在這篇文章中你將看到,在 Java 程序中互相協(xié)作的線程就好像是在一個(gè)組織良好的機(jī)構(gòu)中工作的職員。

在多線程程序中,線程可以從準(zhǔn)備就緒隊(duì)列中得到,并在可獲得的系統(tǒng) CPU 上運(yùn)行。操作系統(tǒng)可以將線程從處理器移到準(zhǔn)備就緒隊(duì)列或阻塞隊(duì)列中,這種情況可以認(rèn)為是處理器“掛起”了該線程。同樣,Java 虛擬機(jī) (JVM) 也可以控制線程的移動(dòng)在協(xié)作或搶先模型中從準(zhǔn)備就緒隊(duì)列中將進(jìn)程移到處理器中,于是該線程就可以開(kāi)始執(zhí)行它的程序代碼。

協(xié)作式線程模型允許線程自己決定什么時(shí)候放棄處理器來(lái)等待其他的線程。程序開(kāi)發(fā)員可以精確地決定某個(gè)線程何時(shí)會(huì)被其他線程掛起,允許它們與對(duì)方有效地合作。缺點(diǎn)在于某些惡意或是寫得不好的線程會(huì)消耗所有可獲得的 CPU 時(shí)間,導(dǎo)致其他線程“饑餓”。

在搶占式線程模型中,操作系統(tǒng)可以在任何時(shí)候打斷線程。通常會(huì)在它運(yùn)行了一段時(shí)間(就是所謂的一個(gè)時(shí)間片)后才打斷它。這樣的結(jié)果自然是沒(méi)有線程能夠不公平地長(zhǎng)時(shí)間霸占處理器。然而,隨時(shí)可能打斷線程就會(huì)給程序開(kāi)發(fā)員帶來(lái)其他麻煩。同樣使用辦公室的例子,假設(shè)某個(gè)職員搶在另一人前使用復(fù)印機(jī),但打印工作在未完成的時(shí)候離開(kāi)了,另一人接著使用復(fù)印機(jī)時(shí),該復(fù)印機(jī)上可能就還有先前那名職員留下來(lái)的資料。

搶占式線程模型要求線程正確共享資源,協(xié)作式模型卻要求線程共享執(zhí)行時(shí)間。由于 JVM 規(guī)范并沒(méi)有特別規(guī)定線程模型,Java 開(kāi)發(fā)員必須編寫可在兩種模型上正確運(yùn)行的程序。在了解線程以及線程間通訊的一些方面之后,我們可以看到如何為這兩種模型設(shè)計(jì)程序。

線程和 Java 語(yǔ)言

為了使用 Java 語(yǔ)言創(chuàng)建線程,你可以生成一個(gè) Thread 類(或其子類)的對(duì)象,并給這個(gè)對(duì)象發(fā)送 start() 消息。(程序可以向任何一個(gè)派生自 Runnable 接口的類對(duì)象發(fā)送 start() 消息。)每個(gè)線程動(dòng)作的定義包含在該線程對(duì)象的 run() 方法中。run 方法就相當(dāng)于傳統(tǒng)程序中的 main() 方法;線程會(huì)持續(xù)運(yùn)行,直到 run() 返回為止,此時(shí)該線程便死了。

上鎖

大多數(shù)應(yīng)用程序要求線程互相通信來(lái)同步它們的動(dòng)作。在 Java 程序中最簡(jiǎn)單實(shí)現(xiàn)同步的方法就是上鎖。為了防止同時(shí)訪問(wèn)共享資源,線程在使用資源的前后可以給該資源上鎖和開(kāi)鎖。假想給復(fù)印機(jī)上鎖,任一時(shí)刻只有一個(gè)職員擁有鑰匙。若沒(méi)有鑰匙就不能使用復(fù)印機(jī)。

給共享變量上鎖就使得 Java 線程能夠快速方便地通信和同步。某個(gè)線程若給一個(gè)對(duì)象上了鎖,就可以知道沒(méi)有其他線程能夠訪問(wèn)該對(duì)象。即使在搶占式模型中,其他線程也不能夠訪問(wèn)此對(duì)象,直到上鎖的線程被喚醒、完成工作并開(kāi)鎖。那些試圖訪問(wèn)一個(gè)上鎖對(duì)象的線程通常會(huì)進(jìn)入睡眠狀態(tài),直到上鎖的線程開(kāi)鎖。一旦鎖被打開(kāi),這些睡眠進(jìn)程就會(huì)被喚醒并移到準(zhǔn)備就緒隊(duì)列中。

在 Java 編程中,所有的對(duì)象都有鎖。線程可以使用 synchronized 關(guān)鍵字來(lái)獲得鎖。在任一時(shí)刻對(duì)于給定的類的實(shí)例,方法或同步的代碼塊只能被一個(gè)線程執(zhí)行。這是因?yàn)榇a在執(zhí)行之前要求獲得對(duì)象的鎖。繼續(xù)我們關(guān)于復(fù)印機(jī)的比喻,為了避免復(fù)印沖突,我們可以簡(jiǎn)單地對(duì)復(fù)印資源實(shí)行同步。

如同下列的代碼例子,任一時(shí)刻只允許一位職員使用復(fù)印資源。通過(guò)使用方法(在 Copier 對(duì)象中)來(lái)修改復(fù)印機(jī)狀態(tài)。這個(gè)方法就是同步方法。只有一個(gè)線程能夠執(zhí)行一個(gè) Copier 對(duì)象中同步代碼,因此那些需要使用 Copier 對(duì)象的職員就必須排隊(duì)等候。

 

  1. class CopyMachine {  
  2. public synchronized void makeCopies(Document d, int nCopies) {  
  3. // only one thread executes this at a time  
  4. }  
  5. public void loadPaper() {  
  6. // multiple threads could access this at once!  
  7. synchronized(this) {  
  8. // only one thread accesses this at a time  
  9. // feel free to use shared resources, overwrite members, etc.  
  10. }  
  11. }  

 

Fine-grain 鎖

在對(duì)象級(jí)使用鎖通常是一種比較粗糙的方法。為什么要將整個(gè)對(duì)象都上鎖,而不允許其他線程短暫地使用對(duì)象中其他同步方法來(lái)訪問(wèn)共享資源?如果一個(gè)對(duì)象擁有多個(gè)資源,就不需要只為了讓一個(gè)線程使用其中一部分資源,就將所有線程都鎖在外面。由于每個(gè)對(duì)象都有鎖,可以如下所示使用虛擬對(duì)象來(lái)上鎖:

 

  1. class FineGrainLock {  
  2. MyMemberClass x, y;  
  3. Object xlock = new Object(), ylock = new Object();  
  4. public void foo() {  
  5. synchronized(xlock) {  
  6. // access x here  
  7. }  
  8. // do something here - but don't use shared resources  
  9. synchronized(ylock) {  
  10. // access y here  
  11. }  
  12. }  
  13. public void bar() {  
  14. synchronized(this) {  
  15. // access both x and y here  
  16. }  
  17. // do something here - but don't use shared resources  
  18. }  

 

若為了在方法級(jí)上同步,不能將整個(gè)方法聲明為 synchronized 關(guān)鍵字。它們使用的是成員鎖,而不是 synchronized 方法能夠獲得的對(duì)象級(jí)鎖。

信號(hào)量

通常情況下,可能有多個(gè)線程需要訪問(wèn)數(shù)目很少的資源。假想在服務(wù)器上運(yùn)行著若干個(gè)回答客戶端請(qǐng)求的線程。這些線程需要連接到同一數(shù)據(jù)庫(kù),但任一時(shí)刻只能獲得一定數(shù)目的數(shù)據(jù)庫(kù)連接。你要怎樣才能夠有效地將這些固定數(shù)目的數(shù)據(jù)庫(kù)連接分配給大量的線程?一種控制訪問(wèn)一組資源的方法(除了簡(jiǎn)單地上鎖之外),就是使用眾所周知的信號(hào)量計(jì)數(shù) (counting semaphore)。

信號(hào)量計(jì)數(shù)將一組可獲得資源的管理封裝起來(lái)。信號(hào)量是在簡(jiǎn)單上鎖的基礎(chǔ)上實(shí)現(xiàn)的,相當(dāng)于能令線程安全執(zhí)行,并初始化為可用資源個(gè)數(shù)的計(jì)數(shù)器。例如我們可以將一個(gè)信號(hào)量初始化為可獲得的數(shù)據(jù)庫(kù)連接個(gè)數(shù)。一旦某個(gè)線程獲得了信號(hào)量,可獲得的數(shù)據(jù)庫(kù)連接數(shù)減一。線程消耗完資源并釋放該資源時(shí),計(jì)數(shù)器就會(huì)加一。

當(dāng)信號(hào)量控制的所有資源都已被占用時(shí),若有線程試圖訪問(wèn)此信號(hào)量,則會(huì)進(jìn)入阻塞狀態(tài),直到有可用資源被釋放。

信號(hào)量最常見(jiàn)的用法是解決“消費(fèi)者-生產(chǎn)者問(wèn)題”。當(dāng)一個(gè)線程進(jìn)行工作時(shí),若另外一個(gè)線程訪問(wèn)同一共享變量,就可能產(chǎn)生此問(wèn)題。消費(fèi)者線程只能在生產(chǎn)者線程完成生產(chǎn)后才能夠訪問(wèn)數(shù)據(jù)。使用信號(hào)量來(lái)解決這個(gè)問(wèn)題,就需要?jiǎng)?chuàng)建一個(gè)初始化為零的信號(hào)量,從而讓消費(fèi)者線程訪問(wèn)此信號(hào)量時(shí)發(fā)生阻塞。

每當(dāng)完成單位工作時(shí),生產(chǎn)者線程就會(huì)向該信號(hào)量發(fā)信號(hào)(釋放資源)。每當(dāng)消費(fèi)者線程消費(fèi)了單位生產(chǎn)結(jié)果并需要新的數(shù)據(jù)單元時(shí),它就會(huì)試圖再次獲取信號(hào)量。因此信號(hào)量的值就總是等于生產(chǎn)完畢可供消費(fèi)的數(shù)據(jù)單元數(shù)。這種方法比采用消費(fèi)者線程不停檢查是否有可用數(shù)據(jù)單元的方法要高效得多。因?yàn)橄M(fèi)者線程醒來(lái)后,倘若沒(méi)有找到可用的數(shù)據(jù)單元,就會(huì)再度進(jìn)入睡眠狀態(tài),這樣的操作系統(tǒng)開(kāi)銷是非常昂貴的。

盡管信號(hào)量并未直接被 Java 語(yǔ)言所支持,卻很容易在給對(duì)象上鎖的基礎(chǔ)上實(shí)現(xiàn)。一個(gè)簡(jiǎn)單的實(shí)現(xiàn)方法如下所示:

 

  1. class Semaphore {  
  2. private int count;  
  3. public Semaphore(int n) {  
  4. this.count = n;  
  5. }  
  6. public synchronized void acquire() {  
  7. while(count == 0) {  
  8. try {  
  9. wait();  
  10. catch (InterruptedException e) {  
  11. // keep trying  
  12. }  
  13. }  
  14. count--;  
  15. }  
  16. public synchronized void release() {  
  17. count++;  
  18. notify(); // alert a thread that's blocking on this semaphore  
  19. }  

 

常見(jiàn)的上鎖問(wèn)題

不幸的是,使用上鎖會(huì)帶來(lái)其他問(wèn)題。讓我們來(lái)看一些常見(jiàn)問(wèn)題以及相應(yīng)的解決方法:

死鎖。死鎖是一個(gè)經(jīng)典的多線程問(wèn)題,因?yàn)椴煌木€程都在等待那些根本不可能被釋放的鎖,從而導(dǎo)致所有的工作都無(wú)法完成。假設(shè)有兩個(gè)線程,分別代表兩個(gè)饑餓的人,他們必須共享刀叉并輪流吃飯。他們都需要獲得兩個(gè)鎖:共享刀和共享叉的鎖。假如線程 "A" 獲得了刀,而線程 "B" 獲得了叉。線程 A 就會(huì)進(jìn)入阻塞狀態(tài)來(lái)等待獲得叉,而線程 B 則阻塞來(lái)等待 A 所擁有的刀。這只是人為設(shè)計(jì)的例子,但盡管在運(yùn)行時(shí)很難探測(cè)到,這類情況卻時(shí)常發(fā)生。雖然要探測(cè)或推敲各種情況是非常困難的,但只要按照下面幾條規(guī)則去設(shè)計(jì)系統(tǒng),就能夠避免死鎖問(wèn)題:

讓所有的線程按照同樣的順序獲得一組鎖。這種方法消除了 X 和 Y 的擁有者分別等待對(duì)方的資源的問(wèn)題。

將多個(gè)鎖組成一組并放到同一個(gè)鎖下。前面死鎖的例子中,可以創(chuàng)建一個(gè)銀器對(duì)象的鎖。于是在獲得刀或叉之前都必須獲得這個(gè)銀器的鎖。

將那些不會(huì)阻塞的可獲得資源用變量標(biāo)志出來(lái)。當(dāng)某個(gè)線程獲得銀器對(duì)象的鎖時(shí),就可以通過(guò)檢查變量來(lái)判斷是否整個(gè)銀器集合中的對(duì)象鎖都可獲得。如果是,它就可以獲得相關(guān)的鎖,否則,就要釋放掉銀器這個(gè)鎖并稍后再嘗試。

最重要的是,在編寫代碼前認(rèn)真仔細(xì)地設(shè)計(jì)整個(gè)系統(tǒng)。多線程是困難的,在開(kāi)始編程之前詳細(xì)設(shè)計(jì)系統(tǒng)能夠幫助你避免難以發(fā)現(xiàn)死鎖的問(wèn)題。

Volatile 變量. volatile 關(guān)鍵字是 Java 語(yǔ)言為優(yōu)化編譯器設(shè)計(jì)的。以下面的代碼為例:

 

  1. class VolatileTest {  
  2. public void foo() {  
  3. boolean flag = false;  
  4. if(flag) {  
  5. // this could happen  
  6. }  
  7. }  

 

一個(gè)優(yōu)化的編譯器可能會(huì)判斷出 if 部分的語(yǔ)句永遠(yuǎn)不會(huì)被執(zhí)行,就根本不會(huì)編譯這部分的代碼。如果這個(gè)類被多線程訪問(wèn),flag 被前面某個(gè)線程設(shè)置之后,在它被 if 語(yǔ)句測(cè)試之前,可以被其他線程重新設(shè)置。用 volatile 關(guān)鍵字來(lái)聲明變量,就可以告訴編譯器在編譯的時(shí)候,不需要通過(guò)預(yù)測(cè)變量值來(lái)優(yōu)化這部分的代碼。

無(wú)法訪問(wèn)的線程 有時(shí)候雖然獲取對(duì)象鎖沒(méi)有問(wèn)題,線程依然有可能進(jìn)入阻塞狀態(tài)。在 Java 編程中 IO 就是這類問(wèn)題***的例子。當(dāng)線程因?yàn)閷?duì)象內(nèi)的 IO 調(diào)用而阻塞時(shí),此對(duì)象應(yīng)當(dāng)仍能被其他線程訪問(wèn)。該對(duì)象通常有責(zé)任取消這個(gè)阻塞的 IO 操作。造成阻塞調(diào)用的線程常常會(huì)令同步任務(wù)失敗。如果該對(duì)象的其他方法也是同步的,當(dāng)線程被阻塞時(shí),此對(duì)象也就相當(dāng)于被冷凍住了。

其他的線程由于不能獲得對(duì)象的鎖,就不能給此對(duì)象發(fā)消息(例如,取消 IO 操作)。必須確保不在同步代碼中包含那些阻塞調(diào)用,或確認(rèn)在一個(gè)用同步阻塞代碼的對(duì)象中存在非同步方法。盡管這種方法需要花費(fèi)一些注意力來(lái)保證結(jié)果代碼安全運(yùn)行,但它允許在擁有對(duì)象的線程發(fā)生阻塞后,該對(duì)象仍能夠響應(yīng)其他線程。

為不同的線程模型進(jìn)行設(shè)計(jì)

判斷是搶占式還是協(xié)作式的線程模型,取決于虛擬機(jī)的實(shí)現(xiàn)者,并根據(jù)各種實(shí)現(xiàn)而不同。因此,Java 開(kāi)發(fā)員必須編寫那些能夠在兩種模型上工作的程序。

正如前面所提到的,在搶占式模型中線程可以在代碼的任何一個(gè)部分的中間被打斷,除非那是一個(gè)原子操作代碼塊。原子操作代碼塊中的代碼段一旦開(kāi)始執(zhí)行,就要在該線程被換出處理器之前執(zhí)行完畢。

在 Java 編程中,分配一個(gè)小于 32 位的變量空間是一種原子操作,而此外象 double 和 long 這兩個(gè) 64 位數(shù)據(jù)類型的分配就不是原子的。使用鎖來(lái)正確同步共享資源的訪問(wèn),就足以保證一個(gè)多線程程序在搶占式模型下正確工作。

而在協(xié)作式模型中,是否能保證線程正常放棄處理器,不掠奪其他線程的執(zhí)行時(shí)間,則完全取決于程序員。調(diào)用 yield() 方法能夠?qū)?dāng)前的線程從處理器中移出到準(zhǔn)備就緒隊(duì)列中。另一個(gè)方法則是調(diào)用 sleep() 方法,使線程放棄處理器,并且在 sleep 方法中指定的時(shí)間間隔內(nèi)睡眠。

正如你所想的那樣,將這些方法隨意放在代碼的某個(gè)地方,并不能夠保證正常工作。如果線程正擁有一個(gè)鎖(因?yàn)樗谝粋€(gè)同步方法或代碼塊中),則當(dāng)它調(diào)用 yield() 時(shí)不能夠釋放這個(gè)鎖。

這就意味著即使這個(gè)線程已經(jīng)被掛起,等待這個(gè)鎖釋放的其他線程依然不能繼續(xù)運(yùn)行。為了緩解這個(gè)問(wèn)題,***不在同步方法中調(diào)用 yield 方法。將那些需要同步的代碼包在一個(gè)同步塊中,里面不含有非同步的方法,并且在這些同步代碼塊之外才調(diào)用 yield。

另外一個(gè)解決方法則是調(diào)用 wait() 方法,使處理器放棄它當(dāng)前擁有的對(duì)象的鎖。如果對(duì)象在方法級(jí)別上使同步的,這種方法能夠很好的工作。因?yàn)樗鼉H僅使用了一個(gè)鎖。如果它使用 fine-grained 鎖,則 wait() 將無(wú)法放棄這些鎖。此外,一個(gè)因?yàn)檎{(diào)用 wait() 方法而阻塞的線程,只有當(dāng)其他線程調(diào)用 notifyAll() 時(shí)才會(huì)被喚醒。

線程和AWT/Swing

在那些使用 Swing 和/或 AWT 包創(chuàng)建 GUI (用戶圖形界面)的 Java 程序中,AWT 事件句柄在它自己的線程中運(yùn)行。開(kāi)發(fā)員必須注意避免將這些 GUI 線程與較耗時(shí)間的計(jì)算工作綁在一起,因?yàn)檫@些線程必須負(fù)責(zé)處理用戶時(shí)間并重繪用戶圖形界面。換句話來(lái)說(shuō),一旦 GUI 線程處于繁忙,整個(gè)程序看起來(lái)就象無(wú)響應(yīng)狀態(tài)。

Swing 線程通過(guò)調(diào)用合適方法,通知那些 Swing callback (例如 Mouse Listener 和 Action Listener )。 這種方法意味著 listener 無(wú)論要做多少事情,都應(yīng)當(dāng)利用 listener callback 方法產(chǎn)生其他線程來(lái)完成此項(xiàng)工作。目的便在于讓 listener callback 更快速返回,從而允許 Swing 線程響應(yīng)其他事件。

如果一個(gè) Swing 線程不能夠同步運(yùn)行、響應(yīng)事件并重繪輸出,那怎么能夠讓其他的線程安全地修改 Swing 的狀態(tài)?正如上面提到的,Swing callback 在 Swing 線程中運(yùn)行。因此他們能修改 Swing 數(shù)據(jù)并繪到屏幕上。

但是如果不是 Swing callback 產(chǎn)生的變化該怎么辦呢?使用一個(gè)非 Swing 線程來(lái)修改 Swing 數(shù)據(jù)是不安全的。Swing 提供了兩個(gè)方法來(lái)解決這個(gè)問(wèn)題:invokeLater() 和 invokeAndWait()。為了修改 Swing 狀態(tài),只要簡(jiǎn)單地調(diào)用其中一個(gè)方法,讓 Runnable 的對(duì)象來(lái)做這些工作。

因?yàn)?Runnable 對(duì)象通常就是它們自身的線程,你可能會(huì)認(rèn)為這些對(duì)象會(huì)作為線程來(lái)執(zhí)行。但那樣做其實(shí)也是不安全的。事實(shí)上,Swing 會(huì)將這些對(duì)象放到隊(duì)列中,并在將來(lái)某個(gè)時(shí)刻執(zhí)行它的 run 方法。這樣才能夠安全修改 Swing 狀態(tài)。

總結(jié)

Java 語(yǔ)言的設(shè)計(jì),使得多線程對(duì)幾乎所有的 Applet 都是必要的。特別是,IO 和 GUI 編程都需要多線程來(lái)為用戶提供***的體驗(yàn)。如果依照本文所提到的若干基本規(guī)則,并在開(kāi)始編程前仔細(xì)設(shè)計(jì)系統(tǒng)包括它對(duì)共享資源的訪問(wèn)等,你就可以避免許多常見(jiàn)和難以發(fā)覺(jué)的線程陷阱。

責(zé)任編輯:于鐵 來(lái)源: 互聯(lián)網(wǎng)
相關(guān)推薦

2012-10-11 10:26:44

云計(jì)算應(yīng)用程序部署

2011-04-01 11:01:02

應(yīng)用程序BlackBerryJava

2009-10-27 12:20:06

VB.NET多線程應(yīng)用

2009-10-09 17:01:32

VB.NET多線程

2011-04-01 13:55:24

Java

2012-09-10 10:31:31

IBMdw

2012-09-06 11:18:17

IBMdw

2012-02-06 10:37:07

Java

2019-11-25 10:46:54

Java數(shù)據(jù)庫(kù)收藏

2018-06-22 09:00:00

Java框架Pronghorn

2013-11-14 15:47:29

SDN問(wèn)題答疑

2011-05-06 15:39:55

硒鼓

2010-07-21 09:10:02

Perl常見(jiàn)問(wèn)題

2010-04-28 09:21:05

2011-01-28 09:12:53

jQuery Mobi

2009-06-16 13:48:42

Java多線程

2012-04-28 15:42:25

應(yīng)用推廣常見(jiàn)問(wèn)題經(jīng)驗(yàn)整理

2010-02-24 13:25:22

Python線程應(yīng)用程

2017-08-08 15:22:38

EPSUPS電源

2010-03-25 09:08:43

CentOS配置
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)