package com.packt.tfesenko.multithreading.section3; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Flow; import java.util.concurrent.Flow.Publisher; import java.util.concurrent.Flow.Subscriber; import java.util.concurrent.Flow.Subscription; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.SubmissionPublisher; import java.util.concurrent.TimeUnit; public class Lesson3 { public static void main(String[] args) { // Try this: // SubmissionPublisher<WeatherForecast> weatherForecastPublisher = new OnDemandWeatherForecastPublisher(); // Try this: // SubmissionPublisher<WeatherForecast> weatherForecastPublisher = new SubmissionPublisher<>(); SubmissionPublisher<WeatherForecast> weatherForecastPublisher = new WeatherForecastPublisher(); weatherForecastPublisher.subscribe(new TwitterSubscriber()); weatherForecastPublisher.subscribe(new DatabaseSubscriber()); // Try this in combination with `weatherForecastPublisher = new SubmissionPiblisher<WeatherForecast>();` // for (int i = 0; i < Long.MAX_VALUE; i++) { // weatherForecastPublisher.submit(WeatherForecast.nextRandomWeatherForecast()); // } // close the publisher and associated resources after 10 seconds try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // Comment out when using OnDemandWeatherForecastPublisher which is not AutoClosable weatherForecastPublisher.close(); } /** * Modification of the example from JavaDoc for * java.util.concurrent.SubmissionPublisher<T> */ public static class WeatherForecastPublisher extends SubmissionPublisher<WeatherForecast> { final ScheduledFuture<?> periodicTask; final ScheduledExecutorService scheduler; WeatherForecastPublisher() { // Try this: // super(Executors.newFixedThreadPool(2), Flow.defaultBufferSize()); scheduler = Executors.newScheduledThreadPool(1); periodicTask = scheduler.scheduleAtFixedRate( // // runs submit() () -> submit(WeatherForecast.nextRandomWeatherForecast()), // 500, 500, TimeUnit.MILLISECONDS); } public void close() { periodicTask.cancel(false); scheduler.shutdown(); super.close(); } } /** * Modifications of OneShotPublisher and OneShotSubscription from the documentation: * (https://docs.oracle.com/javase/9/docs/api/java/util/concurrent/Flow.html) * */ public static class OnDemandWeatherForecastPublisher implements Publisher<WeatherForecast> { private final ExecutorService executor = ForkJoinPool.commonPool(); // daemon-based public synchronized void subscribe(Subscriber<? super WeatherForecast> subscriber) { subscriber.onSubscribe(new OnDemandWeatherForecastSubscription(subscriber, executor)); } } static class OnDemandWeatherForecastSubscription implements Subscription { private final Subscriber<? super WeatherForecast> subscriber; private final ExecutorService executor; private Future<?> future; // to allow cancellation OnDemandWeatherForecastSubscription(Subscriber<? super WeatherForecast> subscriber, ExecutorService executor) { this.subscriber = subscriber; this.executor = executor; } public synchronized void request(long n) { if (n > 0) { for (int i = 0; i < n; i++) { future = executor.submit(() -> { subscriber.onNext(WeatherForecast.nextRandomWeatherForecast()); }); } } else if (n < 0) { IllegalArgumentException ex = new IllegalArgumentException(); executor.execute(() -> subscriber.onError(ex)); } } public synchronized void cancel() { if (future != null) future.cancel(false); } } private static final class DatabaseSubscriber implements Flow.Subscriber<WeatherForecast> { private Flow.Subscription subscription; private final String name = "Database Subscriber"; @Override public void onSubscribe(Subscription subscription) { System.out.println(name + " subscribed!"); this.subscription = subscription; subscription.request(1); } @Override public void onNext(WeatherForecast weatherForecast) { System.out.println( Thread.currentThread().getName() + " > Saving to DB: " + weatherForecast); subscription.request(1); } @Override public void onError(Throwable throwable) { } @Override public void onComplete() { } } private static final class TwitterSubscriber implements Flow.Subscriber<WeatherForecast> { private Flow.Subscription subscription; private final String name = "Twitter Subscriber"; @Override public void onSubscribe(Subscription subscription) { System.out.println(name + " subscribed!"); this.subscription = subscription; subscription.request(1); } @Override public void onNext(WeatherForecast weatherForecast) { System.out.println( Thread.currentThread().getName() + " > Twitting: " + weatherForecast); subscription.request(1); } @Override public void onError(Throwable throwable) { System.err.println(name + " got an error: " + throwable.getMessage()); } @Override public void onComplete() { System.out.println(name + " completed."); } } /** * Weather Forecast in United States customary units */ public static class WeatherForecast { private final int temperatureInF; private final int windSpeedInMPH; private final String weatherCondition; private static final Random random = new Random(); private static final String[] allWeatherConditions = new String[] { "☁️", "☀️", "⛅", "🌧", "⛈️" }; public static WeatherForecast nextRandomWeatherForecast() { String weatherCondition = allWeatherConditions[random .nextInt(allWeatherConditions.length)]; int temperatureInF = random.nextInt(95); int windSpeedInMPH = 5 + random.nextInt(30); return new WeatherForecast(temperatureInF, windSpeedInMPH, weatherCondition); } public WeatherForecast(int temperatureInF, int windSpeedInMPH, String weatherCondition) { super(); this.temperatureInF = temperatureInF; this.windSpeedInMPH = windSpeedInMPH; this.weatherCondition = weatherCondition; } @Override public String toString() { return weatherCondition + " " + temperatureInF + "°F wind: " + windSpeedInMPH + "mph"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + temperatureInF; result = prime * result + ((weatherCondition == null) ? 0 : weatherCondition.hashCode()); result = prime * result + windSpeedInMPH; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; WeatherForecast other = (WeatherForecast) obj; if (temperatureInF != other.temperatureInF) return false; if (weatherCondition == null) { if (other.weatherCondition != null) return false; } else if (!weatherCondition.equals(other.weatherCondition)) return false; if (windSpeedInMPH != other.windSpeedInMPH) return false; return true; } } }