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)等
前两个是必要条件,后面几个是程序质量的描述