星期日, 六月 03, 2007

多线程环境下的Observer pattern

在“The Problem with Threads"论文中提到的用来抨击线程模型的实例。懒得说了,看代码直接:
public class ValueHolder{
private List listeners = new LinkedList();
private int value;

public interface Listener{
public void valueChanged(int newValue);
}

public void addListener(Listener listener){
listeners.add(listener);
}

public void setValue(int newValue){
value = newValue;
Iterator i = copyOfListeners.iterator();
while(i.hasNext()){
((Listener)i.next()).valueChanged(newValue);
}
}
}
以上代码存在问题是多线程环境下对listeners的竞争访问。既然如此,那在addListener和setValue方法前添加synchronzied。好了些,不过有死锁的可能,这主要是因为你不知道别人在valueChanged方法中会做什么,在调用这个方法时,你手中已经紧紧握住一把锁了。继续改进:
public class ValueHolder{
private List listeners = new LinkedList();
private int value;

public interface Listener{
public void valueChanged(int newValue);
}

public synchronized void addListener(Listener listener){
listeners.add(listener);
}

public void setValue(int newValue){
List copyOfListeners;

synchronized(this){
value = newValue;
copyOfListeners = new LinkedList(listeners);
}

Iterator i = copyOfListeners.iterator();
while(i.hasNext()){
((Listener)i.next()).valueChanged(newValue);
}
}
}
此种实现跟JDK源码util包中Observable实现类似,把竞争访问和死锁都排除了。不过此种实现仍存在问题。比如线程A和线程B依次调用setValue,然后线程B抢在线程A之前通知大家。这样就搞得大家认为ValueHolder中最终value值是线程A设置的值,而实际上是线程B设置的值。没办法,只能继续改进。
public class ValueHolder{
private List listeners = new LinkedList();
private int value;
private int seqnum = 0;
private int globalNum = 1;

public interface Listener{
public void valueChanged(int newValue);
}

public synchronized void addListener(Listener listener){
listeners.add(listener);
}

public void setValue(int newValue){
List copyOfListeners;
int localSeqnum;

synchronized(this){
value = newValue;
copyOfListeners = new LinkedList(listeners);
seqnum++;
localSeqnum = seqnum;
}
while(localSeqnum != globalNum){
//Only to wait
}
Iterator i = copyOfListeners.iterator();
while(i.hasNext()){
((Listener)i.next()).valueChanged(newValue);
}
globalNum++;
}
}
以上是我提供的一个实现,不知还有没有问题。关键一点,保证setValue按序执行。

注意:在Java中,局部变量都是线程私有的,不用担心访问冲突,要担心的就是实例变量和类变量。

1 条评论:

xyz 说...

一个原因是java自身只有synchronized这种单一的锁定策略(strategy)。ACE(C++)里很多container有一个模版参数可以配置lock的策略,使用者选择适当的lock就好了。比如:

ACE_Hash_Map<ACE_TString, ACE_TString, ACE_Recursive_Thread_Mutex>

还没有见其他的库有这种设计。