Java多線程設計模式,幫助多線程功能提高質量,降低學習成本。主要的Pattern如下:
1.Single Threaded Execution Pattern 多個線程共享一個實例,這樣的話,多個線程都
擅自改動實例的狀態,實例會喪失安全性。這種情況可以通過Java的關鍵詞synchronized來解決。如多個人
通過一個gate時,只能一個個通過,那么可以如下的方式:
public synchronized void pass(String name){
this.name = name;
}
synchronized方法的性能比普通的方法低,所以降低減少使用。
JDK中很多方法是synchronized,可以安全使用,很多為了性能是沒有同步。為了提高性能可以考慮使用 Immutable Pattern
2.Immutable Pattern 多個線程共享一個實例,但是實例的狀態不會改變,可以提供throughput,但必須保證
不變形(實例的狀態不會改變)。需要使用private,final等來支持。
3.Guarded Suspension Pattern 多個線程共享一個實例,這樣的話,多個線程都
擅自改動實例的狀態,實例會喪失安全性。當實例的狀態不恰當時,就要求線程等待到合適的狀態,以“警戒條 件”來表示實例的“適當的狀態”。如果警戒條件一直不成立,線程會永遠等待下去,會使程序喪失生命性。Java 中用while循環來測試警戒條件,使用wait方法讓線程等待,并使用notify/notifyAll通知警戒條件的改變。
檢 驗、修改警戒條件是,會使用Single Threaded Execution Pattern。Pattern的例子如下:
public class RequestQueue{
private final LinkedList queue = new LinkedList();
public synchronized Request getRequest(){
while(queue.size() <= 0){ //警戒條件
try{
wait();
}catch(InterruptedException e){}
}
return (Request)queue.removeFirst();
}
public synchronized void putRequest(Request request){
queue.addLast(request);
notifyAll();
}
}
以上使用Queue的客戶端和服務器代碼里面非常干凈,沒有多線程的東西,代碼復用性很好。
當警戒條件不成立時想要馬上退出,就使用Balking Pattern
4.Balking Pattern 一直等待安全的時機,會使程序的響應性降低。Java語言中,檢驗警戒條件時要使用if語句
,當要balk時,可使用return退出方法,或者throw拋出異常。
public class Data {
private String filename; //修改是的名字
private String content; // 資料的內容
private boolean changed; //修改后的內容還沒存儲的話,值為true
public Data(String filename, String content) {
this.filename = filename;
this.content = content;
this.changed = true;
}
// 修改資料內容
public synchronized void change(String newContent) {
content = newContent;
changed = true;
}
// 若有資料修改,就存儲到擋安里
public synchronized void save() throws IOException {
if (!changed) {
System.out.println(Thread.currentThread().getName() + " balks");
return; //沒有就退出
}
doSave();
changed = false;
}
// 實際資料儲存到擋案里用的方法
private void doSave() throws IOException {
System.out.println(Thread.currentThread().getName() + " calls doSave, content = " + content);
Writer writer = new FileWriter(filename);
writer.write(content);
writer.close();
}
}
5.Producer-Consumer Pattern 當Producer參與者與Consumer參與者處理的速度不同時,速度慢的會扯速度快的
后腿,而降低程序的throughput。解決的辦法就是在兩者之間,加上中繼用的Channel參與者。并讓Channel
參與者存放多條數據,這樣就可以緩沖Producer和Consumer之間處理速度的差異。這個模式使用了Guarded
Suspension Pattern。
public class Table {
private final String[] buffer;
private int tail; /下一個放put的地方
private int head; //下一個放的take地方
private int count; // buffer內的蛋糕數
public Table(int count) {
this.buffer = new String[count];
this.head = 0;
this.tail = 0;
this.count = 0;
}
// 放置蛋糕
public synchronized void put(String cake) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " puts " + cake);
while (count >= buffer.length) {
wait();
}
buffer[tail] = cake;
tail = (tail + 1) % buffer.length;
count++;
notifyAll();
}
// 取得蛋糕
public synchronized String take() throws InterruptedException {
while (count <= 0) {
wait();
}
String cake = buffer[head];
head = (head + 1) % buffer.length;
count--;
notifyAll();
System.out.println(Thread.currentThread().getName() + " takes " + cake);
return cake;
}
}
6.Read-Write Lock Pattern 多個線程共享一個實例,如進程之間不進行共享胡扯,會喪失安全性。
但使用Single Threaded Execution Pattern會使程序throughput降低。解決的方法就是將控制reader參與者的 鎖定與控制writer參與者的鎖定分開,加入ReadWriteLock參與者,以提供兩種不同的鎖定。
public final class ReadWriteLock {
private int readingReaders = 0; // (A)...實際正在讀取的執行緒數量
private int waitingWriters = 0; // (B)...正在等待寫入的執行緒數量
private int writingWriters = 0; // (C)...實際正在寫入的執行緒數量
private boolean preferWriter = true; // 寫入優先的話,值為true
public synchronized void readLock() throws InterruptedException {
while (writingWriters > 0 || (preferWriter && waitingWriters > 0)) {
wait();
}
readingReaders++; // (A)實際正在讀取的線程數量加1
}
public synchronized void readUnlock() {
readingReaders--; // (A)實際正在讀取的線程數量減1
preferWriter = true;
notifyAll();
}
public synchronized void writeLock() throws InterruptedException {
waitingWriters++; // (B)正在等待寫入的線程數量加1
try {
while (readingReaders > 0 || writingWriters > 0) {
wait();
}
} finally {1
waitingWriters--; // (B)正在等待寫入的線程數量減1
}
writingWriters++; // (C)實際正在寫入的線程數量加1
}
public synchronized void writeUnlock() {
writingWriters--; // (C)實際正在寫入的線程數量減
preferWriter = false;
notifyAll();
}
}
public class Data {
private final char[] buffer;
private final ReadWriteLock lock = new ReadWriteLock();
public Data(int size) {
this.buffer = new char[size];
for (int i = 0; i < buffer.length; i++) {
buffer[i] = '*';
}
}
public char[] read() throws InterruptedException {
lock.readLock();
try {
return doRead();
} finally {
lock.readUnlock();
}
}
public void write(char c) throws InterruptedException {
lock.writeLock();
try {
doWrite(c);
} finally {
lock.writeUnlock();
}
}
private char[] doRead() {
char[] newbuf = new char[buffer.length];
for (int i = 0; i < buffer.length; i++) {
newbuf[i] = buffer[i];
}
slowly();
return newbuf;
}
private void doWrite(char c) {
for (int i = 0; i < buffer.length; i++) {
buffer[i] = c;
slowly();
}
}
private void slowly() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
}
7.Thread-Per-Message Pattern 在方法的屬性處理完成之前,控制權不會從Host參與者退出。如果方法的處理
屬性很話費時間,程序的響應性能會降低。解決的方式就在Host的參與者里,啟動新的線程,并且將該方法應 該進行的工作交給這個心的線程,這樣Client參與者的線程可以繼續執行下一個操作,這樣做,不用更改 Client參與者的程序代碼,并能提高程序的響應性。想節省啟動線程所花費的時間,可以使用Worker Thread Pattern。
public class Host {
private final Helper helper = new Helper();
public void request(final int count, final char c) {
System.out.println(" request(" + count + ", " + c + ") BEGIN");
new Thread() {
public void run() {
helper.handle(count, c);
}
}.start();
System.out.println(" request(" + count + ", " + c + ") END");
}
}
8.Worker Thread Pattern 如果方法的處理屬性很花時間,程序的響應性會降低。為了提供響應性,而啟動新
的線程來處理方法時,啟動線程所花的時間又會降低throughput。另外當送出的請求太多時,會啟動
過多的線程,這會使承載量變差。
public class Channel {
private static final int MAX_REQUEST = 100;
private final Request[] requestQueue;
private int tail; // 下一個putRequest的地方
private int head; // 下一個takeRequest的地方
private int count; // Request的數量
private final WorkerThread[] threadPool;
public Channel(int threads) {
this.requestQueue = new Request[MAX_REQUEST];
this.head = 0;
this.tail = 0;
this.count = 0;
threadPool = new WorkerThread[threads];
for (int i = 0; i < threadPool.length; i++) {
threadPool[i] = new WorkerThread("Worker-" + i, this);
}
}
public void startWorkers() {
for (int i = 0; i < threadPool.length; i++) {
threadPool[i].start();
}
}
public synchronized void putRequest(Request request) {
while (count >= requestQueue.length) {
try {
wait();
} catch (InterruptedException e) {
}
}
requestQueue[tail] = request;
tail = (tail + 1) % requestQueue.length;
count++;
notifyAll();
}
public synchronized Request takeRequest() {
while (count <= 0) {
try {
wait();
} catch (InterruptedException e) {
}
}
Request request = requestQueue[head];
head = (head + 1) % requestQueue.length;
count--;
notifyAll();
return request;
}
}
9.Future Pattern 當Client會將工作委托給其他線程,而Client參與者希望得到處理的結果。將工作委托給
別人時,如果又等待執行結果,會使響應性降低。
public class FutureData implements Data {
private RealData realdata = null;
private boolean ready = false;
public synchronized void setRealData(RealData realdata) {
if (ready) {
return; // balk
}
this.realdata = realdata;
this.ready = true;
notifyAll();
}
public synchronized String getContent() {
while (!ready) {
try {
wait();
} catch (InterruptedException e) {
}
}
return realdata.getContent();
}
}
附多線程程序的評價標準
1、安全性——不損壞對象 對象損壞是指對象的狀態不符合設計師的原意,通常是獲取對象的狀態值并非預期值。
2、生存性——進行必要的處理 也許不是現在,但是一定會進行必要的處理,如果程序安全了,但是有些必要的處理得不到操作,那么這個多線程程序也是不合格的。
3、復用性——可再利用類 寫多線程程序,如果能夠將多線程的共享和互斥結構隱藏在類里面,這就是一個高度可復印的程序。
4、性能——能快速大量處理 主要表現在吞吐量(Throughput)即一定時間內能完成的處理量,能完成的處理量越多,表示數據吞吐量越大;容量(Capacity)指可同時處理的數量;響應性(Responsiveness)指從發出請求到收到響應的時間,時間越短,響應性越高。
5、伸縮性(Scalability)等
前兩個是必要條件,后面幾個是程序質量的描述