状态依赖
概述:
状态依赖实际上就是条件依赖,按照条件的约束的来源可分为两类:
- 外部条件:对象A收到继续执行的请求。
- 内部条件:对象A处于可以继续操作的状态。
***独占技术***关注于维持不变约束,***状态依赖***关注于前提条件和结束条件。
理想的状态,自己编写的类应该尽量避免存在前提条件,并且必然满足结束条件,这样的代码可以规避很多问题。
从活跃性优先和安全性优先的角度设计基于状态的操作的代码有两种:
- 乐观“先试再看”,并不保证成功,要自己处理异常情况。该方式依赖于异常处理机制和合理的提示机制。
- 保守“先测再做”,最大限定缩小入口,一旦满足前提条件,就会成功。该方式依赖于提供保障的构件,这些构件可以在条件满足的时候给出提示,同时保证执行过程中条件一直满足。(保守的方法也可能有异常情况,所以也需要处理异常情况。)
更多的情况下,是二者混合使用。
乐观法:
概述:
乐观法基于乐观的更新和事物。但是有的时候,因为某个调用可能失败也可以被称为“乐观法”,这些调用通常是因为某些前提条件很难校验,但是结束条件一定会验证,且表现形式为捕获异常。
难检查前置条件的几种形式:
- 某些条件因为语言和环境的限制无法检测,比如无法检测待获取的锁是否正在被别人获取,或者指向对象的引用是否唯一。
- 并发程序中,条件是有临时范围的;比如某段时间满足,过会被其他线程修改已经无法满足对应条件了。
- 某些条件依赖于其他线程的异步通知条件,我们无法在当前线程中,判断其他现场的处理情况。
- 某些计算很耗时,校验也耗时,没必要校验,直接进行操作就行。
### 异常
乐观法通常和异常关联很紧密;且并发存在潜在的可能性一部分失败而其余的部分正常运行。但是也可能存在一部分失败,导致其他部分都失败的情况。
当检测到无法达到预计效果的时候,他们就会抛出异常(或者设置失败状态,回调对应的异步通知方法)。通常操作包括六中:突然中断、继续、回滚、前滚、重试、委托给其他的异常处理器;“突然中断”和“继续”是最极端的。“回滚”和“前滚”是保持对象一致性的处理方案。“重试”是处理局部包含失败的方案。“委托”是允许多个对象和活动协同处理失败的操作。选择哪一种方法处理失败必须被统一确认。但有事支持多种方法,让客户自己去选择也是可能的。
突然中断
突然中断,多是处理不可逆转的失败情况的处理方案,同时也是处理未捕获(及未声明)的RuntimeException的默认策略,如抛出NullPointerException的情况。
公用对象在某个程序中被突然中断,应该设置对象破坏的标志。其他的线程操作该对象的时候检查公用对象的标识,发现标识已经被置失败;这些线程应该全部失败,直到该对象以某种方式被修复后才可以继续使用。
继续
java程序中的表现形式多事吞掉了异常,继续往下运行程序(如果可以确定异常不影响业务逻辑,如事件框架中,某个事件失败了),这种情况多见于文件打开的场景。
回滚
乐观的方法多依赖于,该种操作方式-失败恢复(要么成功,要么还原状态,注意没有其他的线程在同时修改该对象,即依赖于锁)
有两种方式实现***回滚操作***:临时性操作、设置检查点
- 临时性操作:创建临时对象,修改的时候修改临时对象,修改成功后让引用指向临时对象,这样失败的时候不用有任何的撤销操作
- 设置检查点:即历史镜像,如果失败就还原历史版本。
***临时性操作***适用于非完全同步的情况,这样避免其他线程看到不一致、和部分更新对象的情况。并且读多写少的情况下效率也很高。***设置检查点***方法更加简单,一般优先选择该方法。二者都不用完全复制对象,新增一些新的属性就可以了。
回滚还有一些特例,如在消息通信框架中,回滚的操作就是发送反向消息(add-delete),可以理解为前滚操作。某些操作是无效的,如网站的公告,这个时候可以发布反向公告如xxxx是无效的。
大多数IO操作是无法回滚的(流一般是一次性消费的东西)通常的做法是新创建一个。
前滚
即当前程序无法执行回滚操作了,如更新某个对象且提交数据库了,这个时候通过对异常的捕获想还原对象是不能的了,只能继续执行,如在finally里面再嵌入一个update语句,还原数据达到恢复原来状态,这就是前滚操作。前滚要求一定要运行到安全点,中间不能被打断。
重试
当失败的原因是由于其他对象临时处于错误状态或者非期望状态而造成的,重试机制通常是可行的。
重试需要控制重试的频率,否则会大量占用CPU,常用的策略是指数让步法,每次重试时间指数长于上次的间隔时间。
class ClientUsingSocket{
Socket retryUntilConnected()throws InterrruptedException{
long delayTime = 5000 +(long)(Math.random() *5000);
for(;;){
try{
return new Socket(server,portnumber);
}catch(IoException ex){
Thread.sleep(delayTime);
delayTime = delayTime * 3/2 +1;
}
}
}
}
异常处理器
集中处理异常,或者需要其他线程或者构建协同解决问题的时候,或者要发布异常通知的时候,可以使用该方法。具体参考Spring Mvc的异常处理器。
取消
概述:
并发程序中随时面临失败或改变执行流程的情况,需要执行取消操作。有两个需要考虑的东西:随时的错误情况,以及确保错误取消的时候对象处于一致的状态。
取消操作的几种情况:
- 用户请求的取消:用户主动点击“取消”按钮
- 限时活动:有时间现在的线程活动。
- 应用程序事件:如爬虫,多线程爬,且各自搜索路径相互隔离,一种找到目标对象,取消所有其他运行的线程。
- 错误:线程错误发生的时候。
- 关闭:主动关闭进程,如果关闭演示动画。
取消操作的几种方式:
- 中断
- IO和资源失效,IO相关操作响应中断的策略
- 异步终止(废弃使用,十分危险)
- 资源控制(控制外部不可控代码对资源的访问,主动取消他们获取资源的权限)
- 多阶段取消健壮性,代码的保证。
中断
java代码中最优先推荐的,取消线程的方式。这是一种协作机制。
可以通过调用Thread.interrupt方法设置中断状态,调用Thread.isInterrupted来查询中断状态,调用Thread.interrupted来查询并且清除中断状态,来通过抛出InterruptedException来响应中断状态。
大多数情况下interrupt会导致线程终止,但是并不保证立即终止。这种协作机制,使得编写健壮的代码成为可能,在执行任何耗时,难回复的安全点之前都应该检查线程状态(前提条件)。终止状态的具体处理方案,即上面的异常处理内容(终止、忽略、回滚、前滚、异常处理器)。不过请注意共享对象的一致性问题。
对响应度要求高的程序,需要设置合理的检测频率(通过测试)。
抛出InterruptedException的时候中断状态被重置了。如果需要在抛出异常后继续传递中断,可以使用重新调用interrupted方法来重新设置中断的状态。
如果调用的外部程序,把中断给吞了,而你又想响应中断,可以在调用中断的地方外部维护一个状态;外部方法调用后检查该状态。
在被***同步锁***和***IO操作***阻塞的时候是无法正常响应中断的。同步锁响应中断可以通过使用锁工具解决问题(wait方法会响应中断),而IO操作解决方案请看下一小节。
IO和资源失效
某些IO类(如java.net.Socket)对于阻塞的读操作,提供了设置超时时间的方法,通过这些方法,可以在超时后检查中断标志,响应中断。
在java.io包中对资源的读取大多没有提供超时的方法,对中断的响应只能在关闭资源(即资源失效)后进行检测,关闭资源操作会导致所有使用该资源的线程的资源不可用(抛出IoException异常),该方案可以避免在多线程共享资源的时候,一个线程被关闭了,影响其他的线程对该资源的操作,JVM的处理方案是不自动中断IO操作。
这样的话,操作资源的线程响应中断的时候就需要做一些额外的工作了,比如线程操作的IO对象是是什么,是否愿意关闭IO来响应中断,如果愿意,就可以通过资源失效后响应中断操作了。
class CancellableReader{
private Thread readerThread;
private FileInputStream dataFile;
public synchronized void startReaderThread() throws IllegalAccessException,FileNotFoundException{
if(readerThread != null) throw new IllegalAccessException();
dataFile = new FileInputStream("data");
readerThread = new Thread(new Runable(){
public void run(){doRead();}
});
readerThread.start();
}
public synchronized void cancelReaderThread(){
if(readerThread != null) readerThread.interrupted();
closeFile();
}
protected synchronized void closeFile(){
if(dataFile != null){
try{dataFile.close();}catch(IOException ignore){}
dataFile = null;
}
}
protected void doRead(){
try{
while(!Thread.interrupted()){
try{
int c = dataFile.read();
if(c==-1) break;
else process(c);
}catch(IOException ex){
break;
}
}
}catch(IOException ex){break}
finally{
closeFile();
synchronized(this){readerThread = null;}
}
}
}
class ReaderWithTimeOut{
//规定时间内到达的流可以处理,之后的就直接失败处理
void attemptRead(InputStream stream,long timeout) throws ...{
long startTime = System.currentTimeMillis();
try{
for(;;){
if(stream.available() > 0){
int c = stream.read();
if(c != -1)process(c);
else break;
}else{
try{
Thread.sleep(100);
}catch(InterruptedException ie){...}
long neow = System.currentTimeMillis();
if(now -startTime >= timeout){
/*fail*/
}
}
}
}catch(IOException ex){}
}
}
异步终止(Thread.stop)
异步终止说的是Thread.stop()方法,该方法发出的是异步信号,你不确定什么时候终止你的程序,且该方法不保证wait、sleep、join方法能够检测到中断的信号。这可能导致验证的安全性问题和数据的一致性问题。
异步终止使的回滚操作也变得困难,因为捕获了ThreadDeath异常,但是不能清楚异常,你的线程的其他地方还会响应该异常,除非你线程中的代码都使用try块包含处理(interrupted方法会清楚中断标志,如果处理完异常,异常不会向下传递了)
资源控制
在外部程序请求我们的资源的时候,我们无法知道对方系统对中断请求的响应策略(可能吞掉信号),但是我们又想将资源的使用时间限制在规定时间内,这个时候我们可以使用java的安全管理机制(SecurityManager)来控制资源的使用。
可以使用setPriority来减少线程对CPU资源的占用率(不可靠),设置安全权限来防止提升现场的优先级别。
多阶段取消
先使用影响最小的关闭策略,然后逐渐加大关闭的策略暴力程度。
class Terminator{
static boolean terminate(Thread t,long maxWaitToDie){
if(!t.isAlive()) return true;
t.interrupt();
try{t.join(maxWaitToDie);}
catch(InterruptedException e){}
if(!t.isAlive)) return true;
theSecurityMgr.denyAllCheckFor(t);
try{t.join(maxWaitToDie);}
catch(InterruptedException e){}
if(!t.isAlive)) return true;
t.setPriority(Thread.MIN_PRIORITY);
return false;
}
}
该取消过程中忽略了对中断的处理,因为如果考虑中断会引入很多其他意想不到的问题。
保守法
保守法依赖于前验条件,当条件未满足的时候有三种处理策略:
- 阻碍:条件不满足抛出异常(拒绝异常,但是效果从客户角度看和失败异常一样),该方案很常见,当条件不满足且以后也很难满足的时候,这个是最好的选择方案。
- 保护性挂起:无限等待条件满足。
- 超时:有时间上限的等待,超过就抛出异常,是上述两策略的中间体。
保护性挂起
该方案是synchronized类型方法的可定制扩展,他是独占的一种扩展形态(synchronized叫做监视器也是这个原因锁+条件);对一个同步的方法保障的含义是-处于就绪状态的对象。也就是说没有任何行为。这个细分了就绪状态,而对应的条件从逻辑层面保证程序的继续运行。
受保障的方法带了了简单条件下不会出现的活跃性问题。
保障隐含是这样一个断言:最终某些线程会改变对象使的条件满足;某些条件不满足程序就不会继续允许。超时的方案就是对这种断言的一种折中方案(解决部分活跃性问题)。
逻辑的控制状态
逻辑的控制状态可以通过谓词来界定,也可以通过角色权限来界定,还可以使用值来界定。
Java的监控机制
Java中主要是Object.wait/Object.notify/Object.notifyAll方法的组合。实现机制主要保证两点:
- 每一个等待条件,写一个受保护的wait循环。
- 条件满足的时候,要通知所有等待的线程。
Object既是锁,又包含一套等待集合(可以由wait、notify、notifyAll、Thread.interrupt操作)。
同时拥有锁和等待集合的实体叫做监视器。
由于等待集合和锁之间的交互机制,只有获得目标对象的同步锁之后,才能调用wait、notify、notifyAll方法,否则抛出IllegalMonitorStateException。
wait的操作顺序:如果现场被中断,抛出异常---->JVM把目标线程放入目标对象内部无法访问的等待集合中-----》目标对象持有的锁被释放。
nofity的操作顺序:随机从等待集合中移除一个线程T---->T必须重新获取锁,阻塞等待一直到调用notify的线程释放锁----->如果之前有其他线程获取锁,T一直阻塞----->T从wait点恢复执行。notifyAll功效类似
Interrupt:阻塞线程在被唤醒的时候抛出异常,但是interrupt和notify同时发生,操作执行的顺序是随机的。
wait方法有一个超时版本,但是当时间维度过小的时候,因为线程的调度策略和时间颗粒度问题,响应时间可能在理想时间的前后有误差(小于ms的参数,JVM实现可能有1~20毫秒的可察觉的反应时间)
wait方法必须被while方法包裹,因为内置锁对象只有一个等待集合,可能有很多条件导致线程被阻塞,但是被唤醒的时候可能条件不满足;而且也有可能是系统层面的假唤醒导致的线程被唤醒。
如果一个线程在调用wait方法的时候被中断,他会直接抛出异常。wait方法先检测中断标志,在处理低下的逻辑。
通知
定时等待
wait方法提供了定时的方法,但是该方法有的时候不准确,需要自己编写超时的代码,来处理超时。具体代码如下:
class TimeoutException extends InterruptedException{....}
class TimeOutBoundedCounter{
protected long count = 0 ;
protected long TIMEOUT = 5000;
synchronized void inc() throws InterruptedException{
if(count >= MAX){
long start = System.currentTimeMillis();
long waitTime =TIMEOUT;
for(;;){
if(waitTime <= 0){
throw new TimeoutException();
}else{
try{
wait(waitTime);
}catch(InterruptedException ie){
throw ie;
}
if(count < MAX)
break;
else{
long now = System.currentTimeMillis();
waitTime = TIMEOUT - (now -start);
}
}
}
}
}
}
忙等待(自旋)
protected void busyWaitUntilCond(){
while(!cond){
Thread.yield();
}
}
-
***效率:***大多数情况下,忙等待的效率远低于wait-notify机制,少数情况下在条件很快到达。(浪费在检测上的时间少于现场挂起、恢复的时间,设备控制系统中多使用该机制。)
-
***调度:***反复的yield只是给JVM一个提示,JVM不保证其他线程能因此或得执行时间片。因为这种实现依赖于JVM,并且可能受线程的相关属性影响。如:循环的线程优先级高,而其他线程优先级低,这个时候可能一直占用执行时间的还是该线程。在基于wait-notify机制中就不会出现该问题。
-
***触发:***条件可能短暂的满足,但是循环没有监测到。(wait-notify机制可能有同样的问题)。且该方案没有办法保证公平性,wait-notify机制有同样的问题。
-
***同步操作:***循环体在锁范围内的时候,其他线程肯能没有办法修改数据,使得条件为真(voliatle可能可以)。wait-notify机制没有该问题。
-
该类的优化实现如下:
class SpinLock{ private volatile boolean busy = false; synchronized void release(){busy = false;} void acquire() throws InterruptedException{ int spinBeforeYield = 100; int spinBeforeSleep = 200; long sleepTime = 1; int spins = 0; for(;;){ if(!busy){ synchronized(this){ if(!busy){ busy = true; return; } } } if(spins < spinBeforeYield){ ++spins; }else if(spins < spinsBeforeSleep){ ++spins; Thread.yield(); }else{ Thread.sleep(sleepTime); sleepTime = 3* sleetpTime /2 + 1; } } } }
通知的类似策略
***通道和有界缓冲区:***该方案主要包括两个方法put和take,类似Sync的acquire和release、类似IO的read和write、类似消息的receive和send操作。有界缓冲区可以被作为通道,他和有界计数器类似,put和take对应inc和dec方法。Min为0,Max为容量。
//通道 interface Channel{ void put(Object x) throws InterruptedException; Object take() throws InterruptedException; } //有界缓冲区 interface BoundedBuffer extends Channel{ int capcity(); // 0<capcity int size(); // 0<= size <= capcity }
具体的实现代码如下:
class BoundedBufferWithStateTracking{ protected final Object[] array; protected int putPtr = 0; protected int takePtr = 0; protected int usedSlots = 0; public BoundedBufferWithStateTracking(int capcity) throws IllegalArgumentException{ if(capcity <= 0) throw new IllegalArgumentException(); array = new Object[capcity]; } public synchronized int size(){return usedSlots;} public int capcity(){return array.length;} public synchronized void put(Object x) throws InterruptedException{ while(usedSlots == array.length) wait(); array[putPtr] = x; putPtr = (putPtr+1)%array.length; if(usedSlots++ ==0){notifyAll();} } public synchronized Object take() throws InterruptedException{ while(usedSlots == 0) wait(); Object x = array[takePtr]; array[takePtr] = null; takePtr = (takePtr + 1)%array.length; if(usedSlots-- = array.length){notifyAll();} return x; } }
/** ** 有界的计数器 **/ class BoundedCounterWithStateVariable{ static final int BOTTOM = 0,MIDDLE = 1,TOP = 2; protected int state = BOTTOM; protected long count = MIN; protected void updateState(){ int oldState = state; if(count =MIN) state = BOTTOM; else if(count == MAX) state = TOP; else state = MIDDLE; if(state != oldState && oldState != MIDDLE){notifyAll();} } public synchronized long count(){return count;} public synchronized void inc()throws InterruptedException{ while(state == TOP) wait(); ++count; updateState(); } public synchronized void dec() throws InterruptedException{ while(state == BOTTOM) wait(); --count; updateState(); } }
某些依赖于通知的方式,可以通过观察行为的冲突集合,解决冲突来改成并行,不在依赖于独占形式的条件(同时是独占的细化)。
例如:Inventory类的store和retrieve,冲突集合有两个{(store,retrieve),(retrieve,retrieve)}
具体的实现:
class Inventory{ protected final Hashtable items = new Hashtable(); protected final Hashtable suppliers = new Hashtable(); protected int storing = 0; protected int retrieving = 0; protected void doStore(String desc,Object item,String supplier){ items.put(desc,item); suppliers.put(supplier,desc); } protected Object doRetrieve(String desc){ Object x = item.get(desc); if(x != null) {item.remove(desc);} return x; } public void store(String desc,Object item,String supplier) throws InterruptedException{ synchronized(this){ while(retrieving != 0){wait();} ++storing; } try{ doStore(desc,item,supplier); }finally{ synchronized(this){ if(--storing == 0) notifyAll(); } } } public Object retrieve(String desc) throws InterruptedException{ synchronized(this){ while(storing!=0 || retrieving !=0) wait(); ++retrieve; } try{ return doRetrieve(desc); }finally{ synchronized(this){ if(--retrieving == 0) notifyAll(); } } } }
abstract class ReadWrite{
protected int activeReaders = 0;
protected int activeWirters = 0;
protected int waitingReaders = 0;
protected int waitingWriters = 0;
protected abstract void doRead();
protected abstarct void doWrite();
public void read()throws InterruptedException{
beforeRead();
try{doRead();}
finally{afterRead();}
}
public void write()throws InterruptedException{
beforeWrite();
try{doWrite();}
finally{afterWrite();}
}
protected boolean allowReader(){
return waitingReaders == 0 && activeWirters == 0;
}
protected boolean allowWriter(){
return activeReaders == 0 && activeWirters == 0;
}
protected synchronized void beforeRead() throws InterruptedException{
++waitingReaders;
while(!allowReader()){
try{wait();}
catch(InterruptedException ie){
--waitingReaders;
throws ie;
}
}
--waitingReaders;
++activeReaders;
}
protected synchronized void afterRead(){
--activeReaders;
notifyAll();
}
protected synchronized void beforeWrite() throws InterruptedException{
++waitingWriters;
while(!allowWriter()){
try{wait();}
catch(InterruptedException ie){
--waitingWriters;
throws ie;
}
}
--waitingWriters
++activeWirters
}
protected synchronized void afterWrite(){
--activeWirters;
notifyAll();
}
}
class RWLock extends ReadWrite implements ReadWriteLock{
class RLock implements Sync{
public void acquire()throws InterruptedException{
beforeRead();
}
public void release(){
afterRead();
}
public boolean attempt(long msecs)throws InterruptedException{
return beferRead(msecs);
}
}
class WLock implements Sync{
public void acquire()throws InterruptedException{
beforeWrite();
}
public void release(){
afterWrite();
}
public boolean attempt(long msecs) throws InterruptedException{
return beforeWrite(msecs);
}
protected final RLock rlock = new RLock();
protected final WLock wlock = new WLock();
public Sync readLock(){return rlock;}
public Sync writeLock(){return wlock;}
public boolean beforeRead(long mescs)throws InterruptedException{}
public boolean beforewrite(long mescs)throws InterruptedException{}
}
}
继承变异
子类在父类没有同步的地方引入同步(包括wait-notify机制)应该注意评估代码的影响范围,可能需要改写的代码不仅仅是你的目标代码。
wait/notify+嵌套监视器导致的死锁
内层的对象进入等待集合的时候,还持有外层的锁。外层无法做任何其他的操作。导致死锁
协同操作
状态依赖操作更多的是关注独立对象,协同操作更多的是关注多个对象的协同操作。具体形式用伪代码表示如下:
void joinAction(A a,B b){
WHEN(canPerformAction(a,b))
performAction(a,b);
}
在分布式开发、并发开发、数据库开发中就会面临这种问题。处理这种问题的第一件是就是把含糊不清的目的和定义好的规范转换成实际可以编程的东西。具体考虑下面几点:
- Allocating responsibility. Which object has responsibility for executing the action? One of the participants? All of them? A separate coordinator?
- Detecting conditions. How can you tell when the participants are in the right state to perform the action? Do you ask them by invoking accessors? Do they tell you whenever they are in the right state? Do they tell you whenever they might be in the right state?
- Programming actions. How are actions in multiple objects arranged? Do they need to be atomic? What if one or more of them fails?
- Linking conditions to actions. How do you make sure that the actions occur only under the right conditions? Are false alarms acceptable? Do you need to prevent one or more participants from changing state between testing the condition and performing the action? Do the actions need to be performed when the participants enter the appropriate states, or merely whenever the conditions are noticed to hold? Do you need to prevent multiple objects from attempting to perform the action at the same time?
最通用的解决方案是:所有的参与者处于和是状态的时候,他们之间相互通知,并且一直到执行完,他们才会改变自己的状态。
这个方案让人很容易想到设计模式中的Double_dispatch和Visitor模式,这类问题的解决方案一般依赖于参与者暴露的属性和他们直接的交互。这样参与者之间的耦合度就会很高,而且高度依赖于上下文内容。这些代码就固化的。
主要有两种模式,平展版本(两个对象对等的),有协调者参与类似事物的Xa协议。
具体的实现逻辑如下:
- 定义A和B,A和B都有指向对方的引用,同时也维护这其他参与条件判断的对象的引用。或者通过第三方对象来间接的连接所有的参与者。
- 编写一个或多个方法来执行主操作。选择一个类包含主操作方法,该方法调用另一个参与者中的辅助方法。或者,把主操作的方法定义在第三方的协调类中,随后调用A和B的辅助方法。
- 在每个类中,编写另一个对象状态改变时候调用的synchronzied方法。例如,在A中编写Bchange方法,在B中,编写Achange方法。每个方法也包含检测自己状态是否满足的代码。如果执行操作和二者都有关系,那么就要持有二者的锁。
- 在每个类中,当每一个触发相应操作的状态改变的时候,调用另一个对象的change方法。如果有必要,应该确保导致通知的状态改变代码被正确的同步,保证测试-执行序列,才释放在开始改变状态的时候所持有的所有参与者的锁。
- 确保A和B的时候里在互动的时候,他们之间的连接和相应的状态已经被初始化。这可以通过第三方协同器很容易的做到。
当然也有一些简化的策略,如通知、操作总是基于一个参与者,某些步骤可以省略。如果使用闭锁的话,就可以省略同步通知,如果使第三方协调者的话,可以省略锁。
上述的设计可能存在***活跃性问题***,该问题可以通过下面的策略解决:
- 强制定向。所有的改变都通过一个参与者来执行。
- 先后顺序。
- 让步。死锁检查,超时
- 传递令牌。
- 弱化语义。
- 相互调用改成外部调用。
举个例子银行转账互动的例子:
伪代码如下:
void autoTransfer(BankAccount checking,
BankAccount saving,
long threshold,
long maxTransfer){
WHEN(checking.balance()<threshold &&
saving.balance()>=0){
long amount = saving.balance();
if(amount > maxTransfer) amount = maxTransfer;
saving.withdraw(amount);
checking.deposit(amount);
}
}
具体代码如下:
/**
*** 没有必要去添个额外酌协调者。所需的交互操作可以被定义在BankAccount的子类中口
*** 操作可以在支票账户的余额减少时或者储蓄账户余额增加讨被执行C 由于导致这两者改变的操作就是deposit (因为这个例子中的withdraw 调用的也是deposit),所以每个类中的deposit 方法都会引发转账橾作。
*** 只有支票账户需要知道threshold,目只有储蓄账户需要了解maxTransfer 值(其他因素可能会导致略微不同的实现),
*** 在储蓄账户这方,条件检测和操作的代码可以被定义个transferOut 方法中`如果不需要转账,那么其返回0 ,否则,它会扣除相应的金额并返回转账的金额数
*** 在支票账户这方,tryTransfer 方法可以处理支票账户和储蓄账户所引起的转账操作勺
**/
class BankAccount{
protected long balance = 0;
public synchronized long balance(){
return balance;
}
public synchronized void deposit(long amount)
throws InsufficientFunds{
if(balance + amount <0){
throw new InsufficientFunds();
}else{
balance += amount;
}
}
public void withdraw(long amount) throw InsufficientFunds{
deposit(-amount);
}
}
class TSBoolean{
private boolean value = false;
public synchronized boolean testAndSet(){
boolean oldValue = value;
value = true;
return oldValue;
}
public synchronized void clear(){
value = false;
}
}
//主控制类
//支票帐户
class ATCheckingAccount extends BankAccount{
protected ATSavingAccount savings;
protected long threshold;
protected TSBoolean transferInProgress = new TSBoolean(); //开关,所有的转入、转出都由这个开关控制。
public ATCheckingAccount(long t){threshold = t;}
synchronized void initSavings(ATSavingAccount s){
savings = s;
}
protected boolean shouldTry(){return balance < threshold;}
void tryTransfer(){
if(!transferInProgress.testAndSet()){
try{
synchronized(this){
if(shouldTry()) balance += savings.tranferOut();
}
}finally{transferInProgress.clear();}
}
}
//取钱
public synchronized void deposit(long amount) throws InsufficientFunds{
if(balance + amount <0){
throw InsufficientFunds();
}else{
balance += acount;
tryTransfer();
}
}
}
//余额帐户
class ATSavingAccount extends BankAccount{
protected ATCheckingAccount checking;
protected long maxTransfer;
public ATSavingAccount(long max){
maxTransfer = max;
}
synchronized void initChecking(ATCheckingAccount c){
checking = c;
}
/**
** 转出
**/
synchronized long tranferOut(){
long amount = balance;
if(amount > maxTransfer){
amount = maxTransfer
}
if(amount >= 0){
balance -= amount;
}
return amount;
}
//存钱也触发
public synchronized void deposit(long amount)throws InsufficientFunds{
if(balance + amount <0){
throw new InsufficientFunds();
}else{
balance += amount;
checking.tryTransfer();
}
}
}
分离观察者(锁层面的隔离)
该部分主要讨论观察者模式,观察者模式不需要完全同步(如果完全同步,你可以放弃使用观察者模式了),可以在Subject变化通知Observer之后释放关于Subject的锁,这样Observer读取Subject的时候可能Subject的数据变化了,这样的弱化语义是可以接受的,甚至是期待的。(例如读取温度计的变化)
具体代码如下:
class Subject{
protected double val = 0.0;
protected final CopyOnWriteArrayList observers
= new CopyOnWriteArrayList();
public synchronized double getValue(){return val;}
protected synchronized void setValue(double d){val = d;}
public void attach(Observer o){observers.add(0);}
public void detach(Observer 0){observers.remove(0);}
public void changeValue(double newState){
setValue(newState);
for(Iterator it = observers.iterator();it.hasNext();){
((Observer)(it.next())).changed(this);
}
}
}
class Observer{
protected double cachedState;
protected final Subject subj;
public Observer(Subject s){
subj = s;
cachedState = s.getValue();
display();
}
public synchronized void changed(Subject s){
if(s!=subj) return;
double oldState = cachedState;
cachedState = subj.getValue();
if(oldState != cachedState){
display();
}
}
protected void display(){
System.out.println(cachedState);
}
}