Java多線程設計模式,幫助多線程功能提升質量,下降學習成本。主要的Pattern以下:
1.Singl
e 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();
}
}
附多線程程序的評價標準
一、安全性——不損壞對象 對象損壞是指對象的狀態不符合設計師的原意,一般是獲取對象的狀態值並不是預期值。
二、生存性——進行必要的處理 也許不是如今,可是必定會進行必要的處理,若是程序安全了,可是有些必要的處理得不到操做,那麼這個多線程程序也是不合格的。
三、複用性——可再利用類 寫多線程程序,若是可以將多線程的共享和互斥結構隱藏在類裏面,這就是一個高度可複印的程序。
四、性能——能快速大量處理 主要表如今吞吐量(Throughput)即必定時間內能完成的處理量,能完成的處理量越多,表示數據吞吐量越大;容量(Capacity)指可同時處理 的數量;響應性(Responsiveness)指從發出請求到收到響應的時間,時間越短,響應性越高。
五、伸縮性(Scalability)等
前兩個是必要條件,後面幾個是程序質量的描述