台灣大約 14、15 年前開始引進內容管理系統,一開始最有名的產品是 TeamSite,聽說當時市佔也是世界第一。後來微軟推出 Share Point Server,也是內容管理的產品,但是,兩個產品真的很不一樣,適用的領域也很不一樣。
約 6、7 年前,有出現一款本土的內容管理產品,架構大略如下:
當時他們是以 subversion 來當版本的控管,如果是現在,應該會如上圖改用 Git 吧? 不知道是否產品太陽春,似乎推展的不順利,或是我太孤陋寡聞,只知道我當時任職的公司有買。
我認為這個陽春版的內容管理系統,在一些公司的開發團隊,應該都可以很快的自行開發出來。像是一些電子商務的公司,就可以在傳統的網站架構中,多一台 Git server,這個沒什麼太大成本,卻可以為網站的許多內容進行版本的控管,方便管理員回復特定內容到指定的版本。
Google Code Prettify
2015年10月25日 星期日
Linux / Unix
項目 | 日期 | |
---|---|---|
* |
Environment
| |
1 | scientific linux 6.x 下如何播放音樂和影片? |
2014/07/27
|
2 | 在 scientific linux 上安裝 telnet & ssh service |
2014/12/21
|
3 | 在 ubuntu 上安裝 telnet service |
2014/12/29
|
4 | Ubuntu 上的 PPPoE 設定 |
2020/05/01
|
5 | install GlassFish Server |
2015/06/22
|
6 | 第一次使用 MySQL |
2014/10/10
|
7 | ssh 公鑰認證登入 |
2015/02/16
|
8 | 在 linux 中無法切換身份? (su: Permission denied) |
2017/12/22
|
9 | install Redis |
2018/07/21
|
10 | RHEL8 無法安裝套件 No match for argument: xxx |
2022/03/27
|
11 | install nfs server@Ubuntu |
2023/11/08
|
* |
Shell Scripting
| |
1 | 使用 sort、join 處理 csv 檔案 |
2015/01/16
|
2 | EBCDIC <=> ASCII 間的轉換 |
2015/01/17
|
* |
C Programming
| |
1 | 在 linux 下寫 C 程式 (使用 eclipse/CDT) |
2015/05/21
|
2 | Linux Network Programming - 初體驗 |
2015/05/23
|
3 | byte ordering functions |
2015/05/26
|
4 | fork Function |
2015/06/06
|
2015年10月23日 星期五
Tibco RV / JMS / Apache Kafka
項目 | 日期 | |
---|---|---|
1 | Tibco RV request/reply 的同步與非同步 |
2015/02/07
|
2 | Tibco RV 的 Queue |
2015/05/05
|
3 | Tibco RV - fault tolerance |
2015/07/10
|
4 | JMS: getting started |
2015/08/30
|
5 | JMS: 訊息的標頭、屬性和種類 |
2015/08/30
|
6 | install Kafka (single node) |
2018/08/11
|
Git / Gradle
項目 | 日期 | |
---|---|---|
* |
Git
| |
1 | 安裝最新的 git |
2015/02/11
|
2 | 建立一個 remote git bare repository |
2015/02/24
|
3 | 使用 TortoiseGit 連上 remote bare repository |
2015/02/28
|
4 | JGit - getting started |
2015/10/21
|
5 | JGit - 基本命令 |
2015/10/22
|
6 | JGit - 走訪、差異比對、日誌 |
2015/10/23
|
* |
Gradle
| |
1 | Gradle: task |
2018/02/23
|
2 | Gradle: 編譯、測試 web 專案 |
2017/08/10
|
3 | Gradle: 編譯 spring boot application |
2018/02/26
|
* |
Azure DevOps
| |
1 | Azure DevOps: CI Tutorial |
2022/12/20
|
JGit - 走訪、差異比對、日誌
這是 git 各個階段版本控管的概念圖,非常重要! 雖然會想了解 JGit 的人,一定對這些都很熟了,但是還是貼出來大家看一下,因為這和接下來要談的差異比對有關。
首先看一下我的環境,如下圖所示,分了幾個 branch,故意弄的有點亂是為了測試。
要走訪 git repository,要先確定是要走訪那個 branch? 及要走訪的節點,一般來說,都是走訪 commit 或 tag 所在的節點。當然更詳細一點點的如下圖: (這些圖都是 TortoiseGit 產生的)。
這上面的每個節點的 name 和 object id,是非常重要的,要找到節點,就靠這些了! 下面是這篇要說明的程式,它會走訪被版控的檔案、目錄、並進行差異比對。 前兩篇說明過的。這篇不會重複,如果有疑問請翻閱前兩篇。
1 public class DiffTest { 2 3 @Before 4 public void setUp() throws Exception { 5 } 6 7 @After 8 public void tearDown() throws Exception { 9 } 10 11 //@Test 12 public void testDiff() throws Exception { 13 Repository repo = new FileRepositoryBuilder() 14 .setGitDir(new File("D:/TestJGit/local/.git")) 15 .build(); 16 17 RevTree headTree = getRevTree(repo, "refs/heads/master"); 18 RevTree branchTree = getRevTree(repo, "refs/heads/branch_001"); 19 20 ObjectReader reader = repo.newObjectReader(); 21 List<DiffEntry> listDiffs = compareTree(repo, reader, headTree, branchTree); 22 23 for (DiffEntry diff : listDiffs) { 24 System.out.println(diff); 25 FileMode mode = diff.getNewMode(); 26 System.out.println(mode.toString()); 27 } 28 29 repo.close(); 30 } 31 32 @Test 33 public void testWalk() throws Exception { 34 Repository repo = new FileRepositoryBuilder() 35 .setGitDir(new File("D:/TestJGit/local/.git")) 36 .build(); 37 38 RevTree headTree = getRevTree(repo, "refs/heads/branch_003"); 39 40 try (TreeWalk treeWalk = new TreeWalk(repo)) { 41 treeWalk.addTree(headTree); 42 treeWalk.setRecursive(false); 43 44 while (treeWalk.next()) { 45 String rawPath = new String(treeWalk.getPathString()); 46 FileMode mode = treeWalk.getFileMode(0); 47 System.out.print("rawPath = " + rawPath); 48 if ((mode.getBits() & FileMode.TYPE_FILE) != 0) { 49 System.out.println(" -- file"); 50 } 51 if ((mode.getBits() & FileMode.TYPE_TREE) != 0) { 52 System.out.println(" -- folder"); 53 } 54 System.out.println(mode.toString()); 55 System.out.println(mode.getBits()); 56 System.out.println(treeWalk.getNameString()); 57 } 58 } 59 } 60 61 private List<DiffEntry> compareTree(Repository repo, ObjectReader reader, 62 RevTree newTree, RevTree oldTree) 63 throws IncorrectObjectTypeException, IOException, GitAPIException { 64 65 CanonicalTreeParser newTreeIter = new CanonicalTreeParser(); 66 newTreeIter.resetRoot(reader, newTree.getId()); 67 68 CanonicalTreeParser oldTreeIter = new CanonicalTreeParser(); 69 oldTreeIter.resetRoot(reader, oldTree.getId()); 70 71 Git git = new Git(repo); 72 List<DiffEntry> listDiffs = git.diff().setOldTree(oldTreeIter).setNewTree(newTreeIter).call(); 73 return listDiffs; 74 } 75 76 private RevTree getRevTree(Repository repo, String objectName) 77 throws AmbiguousObjectException, IncorrectObjectTypeException, 78 IOException, MissingObjectException { 79 80 RevTree tree = null; 81 try (RevWalk revWalk = new RevWalk(repo)) { 82 ObjectId objId = repo.resolve(objectName); 83 RevCommit commit = revWalk.parseCommit(objId); 84 tree = commit.getTree(); 85 } 86 87 return tree; 88 } 89 }
- 先找到樹頭: getRevTree() method 就是用 object name (revision string) 去找到樹頭! 像是程式 17、18 行,我們要比對的是 master 和 branch_003 間的差異,就是先把這兩個節點的 object name 傳入 getRevTree() 以取得樹頭。JGit 的觀念多半是,要做什麼事,就會有個相對應的類別,這裡提供的就是 RevWalk 這個類別。簡單說明一下這個 method 的運作。
- 程式第 82 行,resolve() method 可以由 object name 取得 object id,因為很多 method 要的參數是 object id。
- 程式第 83 行,RevWalk 的 parseCommit() 可以根據傳入的 object id 得到參考到 commit 那個節點的物件。
- 由 RevCommit 的 getTree() 即可取得那個 commit 時的所有目錄、檔案,當然這些檔案的內容也是那個當下的內容。
- 差異比對: 在 17、18 行取得檔案的樹狀結構後,在 21 行透過 compareTree() 比對,結果會以 List<DiffEntry> 傳回,這個 method 的寫法我還不太懂,先不解釋。
- TreeWalk (第 40 行) 是 JGit 提供用來走訪檔案樹狀結構的類別,只要將檔案樹狀結構傳入 (第 41 行) 就可以開始走訪各個節點,setRecursive 傳入 false 就只會走訪最上層,要完整的走訪整棵樹就傳入 true。
- 每個節點都可以取得它的 FileMode,這是記錄該節點基本屬性的物件,像是第 48、51 行所顯示的,呼叫 getBits() 後,傳回的整數其實是每個 bit 有它的意義,可以透過遮罩的方式取得各個 bit 的值,上面的程式就示範了判斷該節點是檔案或路徑的方法。
- FileMode 的 toString() 在 API Document 裡雖然有說明,僅適合用在 debug 時,不過,這個 method 可傳回如 100644 這樣的字串,它是 getBits() 取得的數字以 8 進位表示成字串,後面三個數字是像 Unix 的權限表示法,644 就是 rw-r--r--。
Repository repo = new FileRepositoryBuilder()
.setGitDir(new File("D:/TestJGit/local/.git")) .build(); Git git = new Git(repo); LogCommand logCmd = git.log(); ObjectId objId = repo.resolve("refs/remotes/origin/master"); Iterable<RevCommit> revCommit = logCmd.add(objId).call(); Iterator<RevCommit> commitIter = revCommit.iterator(); while (commitIter.hasNext()) { RevCommit commit = commitIter.next(); System.out.println("commiter name = " + commit.getAuthorIdent().getName()); System.out.println("commiter email = " + commit.getAuthorIdent().getEmailAddress()); Date date = new Date(((long) commit.getCommitTime()) * 1000); System.out.println("commit time = " + date.toString()); }
2015年10月22日 星期四
JGit - 基本命令
繼上一篇之後,這一篇說明一些 git 基本命令所對應的 API。在開始正式內容前,先說明一下我的環境,在我的電腦裡,有兩個目錄,分別為 D:/TestJGit/local 及 D:/TestJGit/remote,第一個目錄是 git repository 第二個是 bare repository,是第一個 repository 的遠端。
只要牽涉到 remote 端,就有認證的問題,照理說要呼叫 setCredentialsProvider,這裡沒有呼叫這個 method,是因為我的 remote 端也在自己電腦的硬碟裡。(setCredentialsProvider 的用法請參考上一篇)
這裡比對的是 working directory 和 最後一次 commit 間的差異,更深入的待下一篇說明。
- git init
File file = new File("D:/TestJGit/local");
Git git = Git.init().setDirectory(file).call();
git.close();
這個指令要注意,檔案系統中要已存在該目錄,才可以在該目錄建立 git repository。
- git init --bare
Git git = Git.init().setDirectory(file).setBare(true).call();
setBare 預設是 false,所以第一個程式沒有呼叫這個 method,要建立 bare repository 只要如上呼叫 setBare 並傳入 true 即可。
- git add
File file = new File("D:/TestJGit/local/.git");
Git git = Git.open(file);
AddCommand addCmd = git.add().addFilepattern("folder_1/local_d.txt");
addCmd.call();
git.close();
git 是分散式的版本控管,不管是要存取 local 或 remote 端,都先開啟 local 端的 git repository,要注意上面開始的是真正 git 的檔案所在的目錄 (.git),另一個要注意的是,要加入的檔案,是以 D:/TestJGit/local 目錄開始往下看,所以上面的檔案的完整路徑是 D:/TestJGit/local/folder_1/local_d.txt,不過,傳入時一定要如上傳相對路徑。
- git commit
CommitCommand commitCmd = git.commit().setMessage("add a、b、d");
commitCmd.call();
- git push
PushCommand pushCmd = git.push().setRemote("origin");
pushCmd.call();
- git clone
這裡要注意的是,開啟 remote 端的 bare repository,目錄名稱不是 .git 子目錄,事實上 bare repository 也沒有這個子目錄,這裡傳入 local repository 也可以。另外,setURI 是傳入 remote 端的路徑 (網址),setDirectory 則傳入 local repository 的路徑。File file = new File("D:/TestJGit/remote"); Git git = Git.open(file); CloneCommand cloneCmd = git.cloneRepository() .setBare(false) .setBranch("refs/remotes/origin/master") .setURI("D:/TestJGit/remote") .setDirectory(new File("D:/TestJGit/local3")); cloneCmd.call(); git.close();
- git pull
File file = new File("D:/TestJGit/local3/.git"); Git git = Git.open(file); PullCommand pullCmd = git.pull(); pullCmd.setRemote("origin"); pullCmd.call(); git.close();
- git rm
RmCommand rmCmd = git.rm().addFilepattern("local_a.txt");
rmCmd.call();
- git diff
DiffCommand diffCmd = git.diff();
List<DiffEntry> diffEntry = diffCmd.call();
for(DiffEntry en:diffEntry) {
System.out.println(en.toString());
}
2015年10月21日 星期三
concurrency programming
分類 | 項目 | 日期 | |
---|---|---|---|
1 |
Java
| java.util.concurrent.locks - 臨界區間的讀寫 |
2015/08/13
|
2 |
Java
| java.util.concurrent - Future & Callable |
2015/08/15
|
3 |
Java
| java.util.concurrent - ExecutorService |
2015/08/16
|
4 |
C
| fork Function |
2015/06/06
|
5 |
Design Pattern
| Producer-Consumer Pattern |
2015/10/17
|
JGit - getting started
目前使用 Java 要操作 git,似乎只有 JGit 這組 API,從這篇開始,簡要的說明 JGit 的使用方法。在 JGit 中,最重要的兩個類別是 Repository 及 Git,Repository 類別可以用來存取 Git Repository (Git 容器) 中的屬性,Git 類別則提供了 Git 相關的指令。接下來舉一些實例:
- 創建 Repository、Git
Repository repo = new FileRepositoryBuilder() .setGitDir(new File("D:/BitBucket/VersionControl/.git")) .build(); Git git = new Git(repo);
我的電腦中有一個目錄 D:/BitBucket/VersionControl,這是一個 git repository,在 BitBucket 這個網站上,有相對應的的一個 git bare repository,在創建 Repository 物件時,路徑指到本機的路徑,至於怎麼存取遠端的 bare repository 內的資料稍後說明。不管是 Repository 類別或 Git 類別,使用完都要呼叫 close() method,以關閉 I/O 連線。
- 取得 remote name
Set<String> remoteNames = repo.getRemoteNames();
- 取得本機 branch
ListBranchCommand branchCmd = git.branchList();
List<Ref> branch = branchCmd.call();
使用 Git 類別時,只要想想 git 有提供那些指令,大至上就可以找到相對的 method,上面的程式會取得 branch 的參考物件 (Ref),如果用 toString() 把它輸出,會如下:
Ref[refs/heads/TEST=e5dc4bc8a01cca3c74a3554f87857d468815b58b] Ref[refs/heads/master=977a0b74fe3e49c166119d473f2415663e158260]
輸出的格式為 Ref[name=objectId],這表示如果我們要操作 branch,就可以透過這個方式取得 name 或 objectId 帶入其它 method 來操作 branch。
- 取得遠端 branch
CredentialsProvider cp = new UsernamePasswordCredentialsProvider("username", "password");Collection<Ref> lsRemote = git.lsRemote() .setCredentialsProvider(cp) .call(); for(Ref remote:lsRemote) { String objName = remote.getName(); Ref localRef = repo.getRef(objName); System.out.println("local ref = " + localRef.toString()); Ref remoteRef = remote.getTarget(); System.out.println("remote ref = " + remoteRef.toString()); }要取得遠端的物件,當然要認證,這裡採用帳號、密碼登入的方式,輸出會如下:
local ref = SymbolicRef[HEAD -> refs/heads/TEST=e5dc4bc8a01cca3c74a3554f87857d468815b58b] remote ref = Ref[HEAD=6c248c9ae67bdf1fc65e3d5cc8110a5c017aba53] local ref = Ref[refs/heads/master=977a0b74fe3e49c166119d473f2415663e158260] remote ref = Ref[refs/heads/master=6c248c9ae67bdf1fc65e3d5cc8110a5c017aba53] local ref = Ref[refs/heads/TEST=e5dc4bc8a01cca3c74a3554f87857d468815b58b] remote ref = Ref[refs/heads/TEST=b6c238f6a641b4272dc0728a9db0ddd93690ce64]
可以看到,local 和 remote 的 object id 不同,對照直接下 git ls-remote 的輸出,就可以發現,上面的 remote ref 與 ls-remote 的輸出是一樣的。
2015年10月18日 星期日
race conditions@web
現在的系統上線前,多半會用資安軟體掃描過,以確定程式碼都符合安全規範,底下是個容易忽略的例子:
【每日一字】
to have no choice but to: to be forced to do something; to have no other option but ...; to have no choice to do anything except ... (除了…外別無選擇,只好,不得不)
* We were lost in the forest, very hungry and tired, but we had no choice but to continue walking to try to get home.
(資料來源: ESL Podcast 287)
public void setUploadFolder(String upload_folder) {
if(!StringUtils.isEmpty(upload_folder)) {
destinationFolder = new File(upload_folder);
destinationFolder.createNewFile();
}
}
這是當使用者上傳檔案後,開啟一個檔案然後準備存檔,為什麼這段程式有問題? 因為,web 程式是多人使用的系統,每個人登入會產生自己的 thread,如果兩個人同時執行到上述的程式,就會產生 race condition,解決的辦法就是加上同步機制,以確保這段程式碼 thread-safe。【每日一字】
to have no choice but to: to be forced to do something; to have no other option but ...; to have no choice to do anything except ... (除了…外別無選擇,只好,不得不)
* We were lost in the forest, very hungry and tired, but we had no choice but to continue walking to try to get home.
(資料來源: ESL Podcast 287)
2015年10月17日 星期六
Producer-Consumer Pattern
實務上,曾經遇過如上的例子,說明如下:
- 資料源每天在規定的時間(約數小時)快速的產生資料,寫到檔案裡,在該時段裡,約會產生近兩億筆的資料,每筆資料中會有不重複的序號。
- 有兩支程式會由檔案讀出資料,第一支是讀出資料後,寫入資料庫,第二支程式則是寫到 MQSeries,不管是寫到資料庫或 MQSeries,當然是為了下游的其它系統的需要。(方框中的兩支程式)
這裡要說明的是方框中的那兩支程式,這兩支程式的架構肯定會很像,因為都是讀檔後輸出,差別只在於輸出的地方不同。實務上看到的程式架構是,主程式只有一個類別,在該類別中讀取資料後輸出,再讀取下一筆,再輸出,也就是循序的,如下圖:
從 I/O 的效率看,讀檔案的速度遠遠大於寫入資料庫的速度,就算讀與寫沒有分開成兩個執行緒,影響應該是很有限。回到第一個圖,這整套系統有兩支這樣的程式,另一支是寫到 MQSeries,所以,又有一支如圖二架構的程式,差別只在輸出的那部份是寫到 MQSeries。這樣的寫法有兩個明顯的問題:
- 程式碼重複。
- 輸入與輸出的部份相耦合,可重用性(reuse)不高。
要改善這個架構,可以套用 Producer-Consumer Pattern (生產者-消費者 範式),這個 pattern 在多執行緒程式中是很常見的,以第二張圖的架構來說, 輸入的部份會成為 producer,輸出的部份成為 consumer,中間加上一個佇列為中介,即可以將這兩部份解耦,如下圖:
套用 pattern 後的程式,輸入與輸出的程式,要面對的都是佇列,而不需要在意另一端是如何實作,實際上資料源是什麼? 或實際要輸出的儲存體是什麼? 也就是說,輸出的部份要寫兩個類別,一個是輸出到資料庫的,一個是輸出到 MQSeries 的,其它部份都不需要改變,只要依需要,換用適當的輸出類別。這個寫法有以下優點:
- 輸入、輸出解耦,可重用性高。
- 解耦後,各自的邏輯變簡單,程式的可讀性提高。
- 輸入、輸出分開在不同執行緒,效率提高。
現在來看一下程式怎麼寫?
1 package idv.steven.patterns; 2 3 import java.nio.ByteBuffer; 4 import java.util.Vector; 5 import java.util.concurrent.TimeUnit; 6 import java.util.concurrent.locks.Condition; 7 import java.util.concurrent.locks.ReentrantLock; 8 9 public class Queue { 10 private Vector<ByteBuffer> pool = new Vector<ByteBuffer>(); 11 private Vector<ByteBuffer> queue = new Vector<ByteBuffer>(); 12 13 private ReentrantLock lock = new ReentrantLock(); 14 private Condition waitQueue = lock.newCondition(); 15 16 private int size = 0; 17 private int capacity = 0; 18 19 public Queue(int size, int capacity) { 20 this.size = size; 21 this.capacity = capacity; 22 23 for(int i=0; i<size; i++) { 24 pool.add(ByteBuffer.allocate(capacity)); 25 } 26 } 27 28 public ByteBuffer alloc() { 29 ByteBuffer buf = null; 30 31 try { 32 buf = pool.remove(0); 33 } 34 catch (ArrayIndexOutOfBoundsException e) { 35 36 } 37 38 return buf; 39 } 40 41 /** 42 * 取得的 ByteBuffer 使用完一定要記得呼叫此 method,否則資源會越來越少。 43 * @param buf 44 */ 45 public void release(ByteBuffer buf) { 46 if (buf.capacity() != capacity) { 47 buf = ByteBuffer.allocate(capacity); 48 } 49 else { 50 buf.clear(); 51 } 52 53 pool.add(buf); 54 } 55 56 public ByteBuffer take() { 57 ByteBuffer buf = null; 58 59 try { 60 buf = queue.remove(0); 61 } 62 catch (ArrayIndexOutOfBoundsException e) { 63 64 } 65 66 if (buf == null) { 67 try { 68 lock.lock(); 69 if (waitQueue.await(10, TimeUnit.SECONDS)) { 70 buf = queue.remove(0); 71 } 72 } catch (InterruptedException | ArrayIndexOutOfBoundsException e) { 73 } 74 finally { 75 lock.unlock(); 76 } 77 } 78 79 return buf; 80 } 81 82 public void put(ByteBuffer buf) { 83 try { 84 lock.lock(); 85 queue.add(buf); 86 waitQueue.signal(); 87 } 88 finally { 89 lock.unlock(); 90 } 91 } 92 }
這個佇列程式在初始化時先產生足夠的 ByteBuffer 備用,這樣效率會比較好,因為這個系統會產生大量的資料,一秒鐘就有超過百筆,如果每次都用 ByteBuffer.allocate(...) 產生,會造成效率上的瓶頸。
在 take() method 中,如果第一次從佇列中取資料,結果佇列沒資料回傳 null,就等 10 秒再取一次。在寫 multi-thread 程式時,盡量不要用 sleep 來等其它 thread,這樣很沒有效率,應該用 await,當其它 thread 完成正等待的事情後 (這裡就是等 producer 產生資料放入佇列後),該 thread 呼叫 signal 就可以繼續往下執行,程式第 86 行就是當有資料放入佇列後,會呼叫 signal。
1 package idv.steven.patterns; 2 3 import java.nio.ByteBuffer; 4 5 public class Consumer implements Runnable { 6 private String name = ""; 7 private Queue queue = null; 8 9 public Consumer(String name, Queue queue) { 10 this.name = name; 11 this.queue = queue; 12 } 13 14 @Override 15 public void run() { 16 ByteBuffer buf = null; 17 18 int i = 1; 19 while ((buf = queue.take()) != null) { 20 byte[] dst = new byte[buf.limit()]; 21 buf.get(dst, 0, buf.limit()); 22 String s = new String(dst); 23 System.out.println(name + "(" + (i++) + "):" + s); 24 queue.release(buf); 25 } 26 } 27 }
Consumer (消費者) 程式收到資料將它輸出到 console,真正的程式當然要輸出到資料庫或 MQSeries,那會複雜的多,這裡為了說明簡化了。
1 package idv.steven.patterns; 2 3 import java.nio.ByteBuffer; 4 import java.util.Random; 5 6 public class Producer implements Runnable { 7 private Queue queue = null; 8 9 @Override 10 public void run() { 11 queue = new Queue(100, 1024); 12 13 Thread tC1 = createConsumer("c1"); 14 Thread tC2 = createConsumer("c2"); 15 Thread tC3 = createConsumer("c3"); 16 17 Random rand = new Random(); 18 19 for(int i=0; i<10; i++) { 20 ByteBuffer buf = queue.alloc(); 21 if (buf != null) { 22 buf.put(String.format("%04d", rand.nextInt(10000)).getBytes()); 23 buf.flip(); 24 queue.put(buf); 25 } 26 } 27 28 try { 29 tC1.join(); 30 tC2.join(); 31 tC3.join(); 32 } catch (InterruptedException e) { 33 } 34 } 35 36 private Thread createConsumer(String name) { 37 Consumer c = new Consumer(name, queue); 38 Thread tConsumer = new Thread(c); 39 tConsumer.start(); 40 41 return tConsumer; 42 } 43 44 public static void main(String[] args) { 45 Producer producer = new Producer(); 46 Thread tProducer = new Thread(producer); 47 tProducer.start(); 48 } 49 }
這個程式是一個 Producer (生產者) 有三個 Consumer (消費者),實際上的應用會依需要而定,所以,Producer-Consumer pattern 會有很多的變形。這裡為了說明方便,由 producer 用亂數產生十個資料,放入佇列中,由 consumer 取出並輸出到 console,佇列中的資料會由那一個 consumer 取得,會依系統的排程決定。輸出的結果如下,當然每次執行會有點差異。
c1(1):2368
c3(1):0728
c2(1):5240
c3(2):3292
c1(2):7614
c3(3):7558
c2(2):8970
c3(4):2345
c1(3):1866
c2(3):5144
佇列也可以更複雜,不同的資料還會有優先權,或是其它各項規則,也可能佇列改成堆壘,這都可視需要而變更。
訂閱:
文章 (Atom)