MST

星途 面试题库

面试题:Java观察者模式在多线程事件驱动场景下的挑战与解决方案

在Java的多线程环境中,使用观察者模式实现事件驱动编程会面临哪些问题,比如线程安全问题等。请详细分析这些问题,并给出对应的解决方案,最好能提供具体代码来展示。
33.8万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

面临的问题

  1. 线程安全问题
    • 分析:在多线程环境下,当被观察对象状态改变并通知观察者时,可能会出现多个线程同时访问和修改观察者状态的情况,导致数据不一致。例如,一个观察者在处理通知时,另一个线程可能同时修改了观察者内部的数据结构。
    • 解决方案:可以使用线程安全的数据结构或者同步机制。比如,使用java.util.concurrent包下的线程安全集合类,或者对关键代码段使用synchronized关键字进行同步。
  2. 通知顺序问题
    • 分析:多线程环境中,由于线程调度的不确定性,观察者收到通知的顺序可能与预期不符。这可能影响到依赖特定通知顺序的业务逻辑。
    • 解决方案:可以维护一个有序的观察者列表,并在通知时按照顺序进行通知。例如,使用java.util.concurrent.ConcurrentSkipListSet来存储观察者,它会按照自然顺序或自定义顺序维护元素。
  3. 竞态条件问题
    • 分析:当多个线程同时尝试注册或取消注册观察者时,可能会出现竞态条件,导致注册或取消操作不完整或不一致。
    • 解决方案:对注册和取消注册操作进行同步。可以使用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等有序集合。