在 ScheduledExecutorService 中使用 Java stream forEach() 冻结

Using Java stream forEach() in ScheduledExecutorService freezes

一般的想法是在后台每 10 秒有一个 Runnable 运行 来检查一些数据,并在需要时对对象进行更改。 ScheduledExecutorService 在方法 main() 中实例化,任务被调度。 Runnable 任务实例化 Crawler 对象并开始爬取。大多数情况下,它会成功运行几次,但是当应用程序 运行 并且数据发生更改时,其中一个爬虫方法会被触发但永远不会结束。代码中没有循环。我试图调试也没有成功。也许你会发现问题所在。

主线:

public class Main {

    public static void main(String[] args) {

        DataStock dataStock = DataStock.getInstance();
        ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
        ses.scheduleAtFixedRate(new EveryFiveSeconds(), 5, 5, TimeUnit.SECONDS);
        // below the task which fails after couple of runs
        ses.scheduleAtFixedRate(new EveryTenSeconds(), 1 , 10, TimeUnit.SECONDS);
        dataStock.init();

        Menu currentScreen = new UserMenu();
        while(currentScreen != null) {
            currentScreen = currentScreen.display();
        }

    }
}

每十秒可运行:

public class EveryTenSeconds implements Runnable {
    @Override
    public void run() {
        Crawler crawler = new Crawler();
        crawler.crawl();
    }
}

抓取工具:

public class Crawler {

    private final DataStock dataStock;

    public Crawler() {
        this.dataStock = DataStock.getInstance();
    }

    public void crawl() {
        checkOutRentables(dataStock.getCarServicesWithOwners().keySet());
        checkFinancialBook(dataStock.getPaymentsBook(), dataStock.getCurrentDate());
    }

    private void checkOutRentables(Set<CarService> carServices) {
        System.out.println("Start check...");
        carServices.stream()
                .flatMap(service -> service.getWarehousesSet().stream())
                .filter(rentable -> !rentable.isAvailableForRent())
                .forEach(RentableArea::refreshCurrentState);
        System.out.println("Checking finished");
    }

    private void checkFinancialBook(Set<BookEntry> bookEntries, LocalDate currentDate) {
        System.out.println("Start second check...");
        bookEntries.stream()
                .filter(bookEntry -> currentDate.isAfter(bookEntry.getPaymentDeadline()) && !bookEntry.isPaid() && !bookEntry.isNotified())
                .forEach(BookEntry::notifyDebtor);
        System.out.println("Finished second check..."); //this line never shows in one of runs and the task is never repeated again...

    }
}

BookEntry

public class BookEntry {
    private final UUID rentableId = UUID.randomUUID();
    private final UUID personId;
    private final UUID id;
    private final BigDecimal amountDue;
    private final LocalDate paymentDeadline;
    private boolean paid = false;
    private boolean notified = false;

    public BookEntry(UUID personId, UUID id, BigDecimal amountDue, LocalDate paymentDeadline) {
        this.personId = personId;
        this.id = id;
        this.amountDue = amountDue;
        this.paymentDeadline = paymentDeadline;
    }

    public UUID getRentableId() {
        return rentableId;
    }

    public UUID getPersonId() {
        return personId;
    }

    public UUID getId() {
        return id;
    }

    public BigDecimal getAmountDue() {
        return amountDue;
    }

    public LocalDate getPaymentDeadline() {
        return paymentDeadline;
    }

    public boolean isPaid() {
        return paid;
    }

    public boolean isNotified() {
        return notified;
    }

    public void settlePayment() {
        if(!paid) {
            paid = true;
        }
        else {
            throw new IllegalStateException("This is already paid man!");
        }
    }

    public void notifyDebtor() {
        if(!notified) {
            notified = true;
            DataStock dataStock = DataStock.getInstance();
            Person debtor = dataStock.getPeople().stream()
                    .filter(person -> person.getId().equals(personId))
                    .findFirst()
                    .orElseThrow();
            debtor.alert(new TenantAlert(personId, rentableId, dataStock.getCurrentDate(), amountDue));
        }
    }
}

答案似乎很简单——每当 ScheduledExecutorService 中计划的任务抛出异常时,任务就会停止并且不再重复。也不会明显抛出异常。避免这种情况的最简单方法是在 运行() 中使用 try-catch 块,即 Runnable 方法。请看看这个post:ScheduledExecutorService handling exceptions