java觀察者模式原始碼分析
一、簡單介紹
(1)觀察者模式:在物件之間定義了一對多的依賴,這樣一來,當一個物件改變狀態,依賴它的物件會收到通知並自動更新。簡單的理解為“釋出---訂閱”。
(2)應用例項:
1、京東上某個商品暫時沒貨,提示使用者關注後到貨通知,這個暫時無貨的商品是被觀察者,點選關注這個商品的使用者就是觀察者。
2、老師針對成績在60分以下的同學定期傳送最新的考題分析郵件,每輪考試下來都會有不及格的同學,由不及格變為及格的同學自動從郵件列表裡移除,新的不及格的同學會被加進郵件列表裡。
(3)優點:
1、觀察者和被觀察者是抽象耦合的。
2、建立一套觸發機制。
(4)缺點:
1、如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
2、如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間進行迴圈呼叫,可能導致系統崩潰。 3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。
(5)使用場景:
1、有多個子類共有的方法,且邏輯相同。
2、重要的、複雜的方法,可以考慮作為模板方法。
(6)注意事項:
1、JAVA 中已經有了對觀察者模式的支援類。
2、避免迴圈引用。
3、如果順序執行,某一觀察者錯誤會導致系統卡殼,一般採用非同步方式後面介紹Rxjava。
二、原始碼分析
(1)觀察者:
public interface Observer { void update(Observable o, Object arg); }
太簡單了,就是一個介面包含一個update方法。
(2)被觀察者:
public class Observable { private boolean changed = false; private Vector<Observer> obs; public Observable() { obs = new Vector<>(); } public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } public void notifyObservers() { notifyObservers(null); } public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } public synchronized void deleteObservers() { obs.removeAllElements(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { changed = false; } public synchronized boolean hasChanged() { return changed; } public synchronized int countObservers() { return obs.size(); } }
也很簡單,方法簡單明瞭。內部使用Vector容器保證執行緒安全,另外基本上每個方法都是synchronized修飾,用來實現同步鎖。唯獨有一個方法值得一提:
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
此方法並沒有進行用synchronized修飾,原因很簡單:A執行緒執行通知並且觀察者update的時候,如果B執行緒也執行通知並且update,如果update是耗時的操作,那麼A、B 執行緒會相互阻塞。所以只是內部使用了同步鎖,鎖住了更新狀態和list的副本獲取,保證資料同步。
三、例項使用
(1)我們自己的觀察者
public class Watcher implements Observer {
@Override
public void update(Observable o, Object arg) {
try {
Thread.sleep(5000);//模擬耗時操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(o.toString()+" "+arg.toString());
}
}
(2)我們自己的被觀察者
public class BeWatcher extends Observable{
public void TestChange(int count){
setChanged();
notifyObservers(count);
}
}
(3)測試類
public class TestObserverMain {
public static void main(String[] args){
BeWatcher beWatcher = new BeWatcher();
Watcher watcher = new Watcher();
beWatcher.addObserver(watcher);
for(int i=0;i<2;i++){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
beWatcher.TestChange((int)Thread.currentThread().getId());
}
});
t.start();
}
}
}