Google Code Prettify

2015年10月25日 星期日

陽春版內容管理系統

台灣大約 14、15 年前開始引進內容管理系統,一開始最有名的產品是 TeamSite,聽說當時市佔也是世界第一。後來微軟推出 Share Point Server,也是內容管理的產品,但是,兩個產品真的很不一樣,適用的領域也很不一樣。

約 6、7 年前,有出現一款本土的內容管理產品,架構大略如下:
當時他們是以 subversion 來當版本的控管,如果是現在,應該會如上圖改用 Git 吧? 不知道是否產品太陽春,似乎推展的不順利,或是我太孤陋寡聞,只知道我當時任職的公司有買。

我認為這個陽春版的內容管理系統,在一些公司的開發團隊,應該都可以很快的自行開發出來。像是一些電子商務的公司,就可以在傳統的網站架構中,多一台 Git server,這個沒什麼太大成本,卻可以為網站的許多內容進行版本的控管,方便管理員回復特定內容到指定的版本。





Linux / Unix

項目日期
*
  Environment
1scientific linux 6.x 下如何播放音樂和影片?
2014/07/27
2在 scientific linux 上安裝 telnet & ssh service
2014/12/21
3在 ubuntu 上安裝 telnet service
2014/12/29
4Ubuntu 上的 PPPoE 設定
2020/05/01
5install GlassFish Server
2015/06/22
6第一次使用 MySQL
2014/10/10
7ssh 公鑰認證登入
2015/02/16
8在 linux 中無法切換身份? (su: Permission denied)
2017/12/22
9install Redis
2018/07/21
10RHEL8 無法安裝套件 No match for argument: xxx
2022/03/27
11install nfs server@Ubuntu
2023/11/08
*
  Shell Scripting
1使用 sort、join 處理 csv 檔案
2015/01/16
2EBCDIC <=> ASCII 間的轉換
2015/01/17
*
  C Programming
1在 linux 下寫 C 程式 (使用 eclipse/CDT)
2015/05/21
2Linux Network Programming - 初體驗
2015/05/23
3byte ordering functions
2015/05/26
4fork Function
2015/06/06

2015年10月23日 星期五

Tibco RV / JMS / Apache Kafka

項目日期
1Tibco RV request/reply 的同步與非同步
2015/02/07
2Tibco RV 的 Queue
2015/05/05
3Tibco RV - fault tolerance
2015/07/10
4JMS: getting started
2015/08/30
5JMS: 訊息的標頭、屬性和種類
2015/08/30
6install 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
4JGit - getting started
2015/10/21
5JGit - 基本命令
2015/10/22
6JGit - 走訪、差異比對、日誌
2015/10/23
*
  Gradle
1Gradle: task
2018/02/23
2Gradle: 編譯、測試 web 專案
2017/08/10
3Gradle: 編譯 spring boot application
2018/02/26
*
  Azure DevOps
1Azure 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 的運作。
  1. 程式第 82 行,resolve() method 可以由 object name 取得 object id,因為很多 method 要的參數是 object id。 
  2. 程式第 83 行,RevWalk 的 parseCommit() 可以根據傳入的 object id 得到參考到 commit 那個節點的物件。 
  3. 由 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--。
差異比對不只可以比對不同 branch 間的差異,在文章一開頭顯示的那一張圖上的所有階段都可以比對。實際看過 API Documents 的人會發現,面的 FileMode 只記錄節點的基本屬性,那比我們想要了解的絕對少的多,要了解更多資訊,要從日誌查,下面的程式說明如何取得日誌 (git log)。
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());
}
透過 LogCommand 可以查得日誌,上面只簡單的舉幾個例子,像是 commit 的帳號、email、時間,詳細當然就請直接查 Documents。上面的時間有乘以 1000,是因為 getCommitTime() 傳回的值只到秒,而 Date 需要的是毫秒。



2015年10月22日 星期四

JGit - 基本命令

上一篇之後,這一篇說明一些 git 基本命令所對應的 API。在開始正式內容前,先說明一下我的環境,在我的電腦裡,有兩個目錄,分別為 D:/TestJGit/local 及 D:/TestJGit/remote,第一個目錄是 git repository 第二個是 bare repository,是第一個 repository 的遠端。
  • 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();
只要牽涉到 remote 端,就有認證的問題,照理說要呼叫 setCredentialsProvider,這裡沒有呼叫這個 method,是因為我的 remote 端也在自己電腦的硬碟裡。(setCredentialsProvider 的用法請參考上一篇)
  • git clone
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();
這裡要注意的是,開啟 remote 端的 bare repository,目錄名稱不是 .git 子目錄,事實上 bare repository 也沒有這個子目錄,這裡傳入 local repository 也可以。另外,setURI 是傳入 remote 端的路徑 (網址),setDirectory 則傳入 local repository 的路徑。
  • 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());
}
這裡比對的是 working directory 和 最後一次 commit 間的差異,更深入的待下一篇說明。



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
如上圖所示,我的 git repository 設定了兩個 remote repository,要如何取得名稱呢? 如下:
Set<String> remoteNames = repo.getRemoteNames();




  •  取得本機 branch
除了預設的 master 外,我又建立了一個 TEST branch,以下是取得 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

現在的系統上線前,多半會用資安軟體掃描過,以確定程式碼都符合安全規範,底下是個容易忽略的例子:

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



實務上,曾經遇過如上的例子,說明如下:

  1. 資料源每天在規定的時間(約數小時)快速的產生資料,寫到檔案裡,在該時段裡,約會產生近兩億筆的資料,每筆資料中會有不重複的序號。
  2. 有兩支程式會由檔案讀出資料,第一支是讀出資料後,寫入資料庫,第二支程式則是寫到 MQSeries,不管是寫到資料庫或 MQSeries,當然是為了下游的其它系統的需要。(方框中的兩支程式)
這裡要說明的是方框中的那兩支程式,這兩支程式的架構肯定會很像,因為都是讀檔後輸出,差別只在於輸出的地方不同。實務上看到的程式架構是,主程式只有一個類別,在該類別中讀取資料後輸出,再讀取下一筆,再輸出,也就是循序的,如下圖:


從 I/O 的效率看,讀檔案的速度遠遠大於寫入資料庫的速度,就算讀與寫沒有分開成兩個執行緒,影響應該是很有限。回到第一個圖,這整套系統有兩支這樣的程式,另一支是寫到 MQSeries,所以,又有一支如圖二架構的程式,差別只在輸出的那部份是寫到 MQSeries。這樣的寫法有兩個明顯的問題:
  1. 程式碼重複。
  2. 輸入與輸出的部份相耦合,可重用性(reuse)不高。
要改善這個架構,可以套用 Producer-Consumer Pattern (生產者-消費者 範式),這個 pattern 在多執行緒程式中是很常見的,以第二張圖的架構來說, 輸入的部份會成為 producer,輸出的部份成為 consumer,中間加上一個佇列為中介,即可以將這兩部份解耦,如下圖:


套用 pattern 後的程式,輸入與輸出的程式,要面對的都是佇列,而不需要在意另一端是如何實作,實際上資料源是什麼? 或實際要輸出的儲存體是什麼? 也就是說,輸出的部份要寫兩個類別,一個是輸出到資料庫的,一個是輸出到 MQSeries 的,其它部份都不需要改變,只要依需要,換用適當的輸出類別。這個寫法有以下優點:
  1. 輸入、輸出解耦,可重用性高。
  2. 解耦後,各自的邏輯變簡單,程式的可讀性提高。
  3. 輸入、輸出分開在不同執行緒,效率提高。




現在來看一下程式怎麼寫?

 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
佇列也可以更複雜,不同的資料還會有優先權,或是其它各項規則,也可能佇列改成堆壘,這都可視需要而變更。