MST

星途 面试题库

面试题:Java观察者模式在多线程环境下的应用及问题处理

在多线程场景中使用Java观察者模式,可能会出现哪些问题?比如线程安全、数据一致性等。请举例说明,并阐述你会如何解决这些问题,给出具体的代码实现思路。
16.8万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

可能出现的问题

  1. 线程安全问题
    • 添加和移除观察者时:在多线程环境下,当多个线程同时对观察者列表进行添加或移除操作时,可能会出现数据不一致。例如,线程A正在添加一个观察者,同时线程B试图移除该观察者,这可能导致观察者列表处于不一致状态。
    • 通知观察者时:如果在通知观察者时,观察者的处理逻辑不是线程安全的,可能会引发问题。比如多个线程同时更新被观察对象的状态,然后通知观察者,观察者在处理更新时可能会出现数据竞争。
  2. 数据一致性问题
    • 被观察对象状态更新和通知观察者的时机不同步。例如,被观察对象的状态已经更新,但由于线程调度等原因,观察者没有及时收到更新通知,导致观察者使用的是旧的数据,从而产生数据不一致。

举例说明

假设我们有一个股票价格监控系统,股票价格是被观察对象,不同的投资者是观察者。

import java.util.ArrayList;
import java.util.List;

// 被观察对象
class StockPrice {
    private double price;
    private List<Investor> investors = new ArrayList<>();

    public void attach(Investor investor) {
        investors.add(investor);
    }

    public void detach(Investor investor) {
        investors.remove(investor);
    }

    public void setPrice(double price) {
        this.price = price;
        notifyInvestors();
    }

    private void notifyInvestors() {
        for (Investor investor : investors) {
            investor.update(price);
        }
    }
}

// 观察者
class Investor {
    private String name;

    public Investor(String name) {
        this.name = name;
    }

    public void update(double price) {
        System.out.println(name + " 收到股票价格更新: " + price);
    }
}

在多线程环境下,假设有多个线程同时操作StockPrice对象的attachdetachsetPrice方法,就可能出现上述线程安全和数据一致性问题。

解决思路及代码实现

  1. 线程安全问题解决
    • 使用线程安全的集合:将观察者列表改为CopyOnWriteArrayList,它在修改时会创建一个新的数组,读操作不会受写操作影响,从而保证线程安全。
    • 同步方法:对关键方法(如attachdetachnotifyInvestors)使用synchronized关键字进行同步。
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

// 被观察对象
class StockPrice {
    private double price;
    private List<Investor> investors = new CopyOnWriteArrayList<>();

    public synchronized void attach(Investor investor) {
        investors.add(investor);
    }

    public synchronized void detach(Investor investor) {
        investors.remove(investor);
    }

    public synchronized void setPrice(double price) {
        this.price = price;
        notifyInvestors();
    }

    private void notifyInvestors() {
        for (Investor investor : investors) {
            investor.update(price);
        }
    }
}

// 观察者
class Investor {
    private String name;

    public Investor(String name) {
        this.name = name;
    }

    public void update(double price) {
        System.out.println(name + " 收到股票价格更新: " + price);
    }
}
  1. 数据一致性问题解决
    • 使用阻塞队列:可以在被观察对象和观察者之间使用阻塞队列。当被观察对象状态更新时,将更新信息放入阻塞队列,观察者从阻塞队列中获取更新信息,这样可以保证观察者获取到的是最新的数据。
    • 使用CountDownLatchCyclicBarrier:在通知观察者之前,使用CountDownLatchCyclicBarrier来确保所有必要的更新操作都已完成,然后再通知观察者。例如,在多个线程更新被观察对象状态后,使用CountDownLatch等待所有更新线程完成,再通知观察者。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

// 被观察对象
class StockPrice {
    private double price;
    private BlockingQueue<Double> priceQueue = new LinkedBlockingQueue<>();
    private List<Investor> investors = new CopyOnWriteArrayList<>();

    public synchronized void attach(Investor investor) {
        investors.add(investor);
    }

    public synchronized void detach(Investor investor) {
        investors.remove(investor);
    }

    public synchronized void setPrice(double price) {
        this.price = price;
        try {
            priceQueue.put(price);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        notifyInvestors();
    }

    private void notifyInvestors() {
        for (Investor investor : investors) {
            new Thread(() -> {
                try {
                    double newPrice = priceQueue.take();
                    investor.update(newPrice);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
}

// 观察者
class Investor {
    private String name;

    public Investor(String name) {
        this.name = name;
    }

    public void update(double price) {
        System.out.println(name + " 收到股票价格更新: " + price);
    }
}

通过上述方法,可以在多线程场景下较好地解决Java观察者模式中的线程安全和数据一致性问题。