面试题答案
一键面试面临的问题
- 线程安全问题
- 分析:在多线程环境下,当被观察对象状态改变并通知观察者时,可能会出现多个线程同时访问和修改观察者状态的情况,导致数据不一致。例如,一个观察者在处理通知时,另一个线程可能同时修改了观察者内部的数据结构。
- 解决方案:可以使用线程安全的数据结构或者同步机制。比如,使用
java.util.concurrent
包下的线程安全集合类,或者对关键代码段使用synchronized
关键字进行同步。
- 通知顺序问题
- 分析:多线程环境中,由于线程调度的不确定性,观察者收到通知的顺序可能与预期不符。这可能影响到依赖特定通知顺序的业务逻辑。
- 解决方案:可以维护一个有序的观察者列表,并在通知时按照顺序进行通知。例如,使用
java.util.concurrent.ConcurrentSkipListSet
来存储观察者,它会按照自然顺序或自定义顺序维护元素。
- 竞态条件问题
- 分析:当多个线程同时尝试注册或取消注册观察者时,可能会出现竞态条件,导致注册或取消操作不完整或不一致。
- 解决方案:对注册和取消注册操作进行同步。可以使用
synchronized
方法或者ReentrantLock
来保证同一时间只有一个线程能进行注册或取消注册操作。
代码示例
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
// 抽象主题(被观察对象)
abstract class Subject {
private List<Observer> observers = new ArrayList<>();
private ReentrantLock lock = new ReentrantLock();
public void attach(Observer observer) {
lock.lock();
try {
observers.add(observer);
} finally {
lock.unlock();
}
}
public void detach(Observer observer) {
lock.lock();
try {
observers.remove(observer);
} finally {
lock.unlock();
}
}
public void notifyObservers() {
lock.lock();
try {
for (Observer observer : observers) {
observer.update(this);
}
} finally {
lock.unlock();
}
}
}
// 具体主题
class ConcreteSubject extends Subject {
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyObservers();
}
}
// 抽象观察者
interface Observer {
void update(Subject subject);
}
// 具体观察者
class ConcreteObserver implements Observer {
private int observerState;
@Override
public void update(Subject subject) {
observerState = ((ConcreteSubject) subject).getState();
System.out.println("Observer state updated to: " + observerState);
}
}
public class EventDrivenProgrammingExample {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer1 = new ConcreteObserver();
ConcreteObserver observer2 = new ConcreteObserver();
subject.attach(observer1);
subject.attach(observer2);
Thread thread1 = new Thread(() -> subject.setState(1));
Thread thread2 = new Thread(() -> subject.setState(2));
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在上述代码中:
- 使用
ReentrantLock
对观察者的注册(attach
)、取消注册(detach
)和通知(notifyObservers
)操作进行同步,解决竞态条件和线程安全问题。 - 虽然没有显式处理通知顺序问题,但通过
ArrayList
存储观察者并按顺序遍历通知,在一定程度上保证了顺序性。如果需要更严格的顺序控制,可以使用前面提到的ConcurrentSkipListSet
等有序集合。