Reactive Web - Servlet & Async, Non-blocking I/O

Preview:

Citation preview

!"

#$

%

'

'

Servlet 2.2

JSP 1.1

Servlet 2.3

JSP 1.2 JSTL 1.0

Servlet 2.4

JSP 2.0 JSTL 1.1

Servlet 2.5

JSP 2.1 EL 2.1 JSTL 1.2

Servlet 3.0

JSP 2.2 EL 2.2 JSTL 1.2

Servlet 3.1

JSP 2.3 EL 3.0 JSTL 1.2

'

!

#%

!

%$

(

%

%

(

"

)

'

"

#$

%

!*

*

GET /hello?name=arwan HTTP/1.1

<html> … </html>

TomcatUndertowJetty

HttpServletRequest HttpServletResponse

@WebServlet(url

Patterns = {"/he

llo"})

class HelloServ

let extends Http

Servlet { … }

'

!"

#$

'

#

#

#

!"

#$

'

#

#

#

$

$

$

{ “content”: “…”}

'

'

! #

'

var es = new EventSource(‘/notification/stream');

es.onmessage = function (event) { // };

es.addEventListener(‘feed-notify', function(event) { // ‘feed-notify' console.log(event.data); }, false);

es.onerror = function (event) { // };

'

@WebServlet("/notification/stream") public class NotificationStreamServlet extends HttpServlet {

final static Log log = LogFactory.getLog(NotificationStreamServlet.class); @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws Exception { log.info("in notification/stream"); response.setCharacterEncoding("utf-8"); response.setContentType("text/event-stream"); try { NotificationStream notificationStream = new NotificationStream(obtainUsername(request)); Iterator<Notification> notifies = notificationStream.feedNotifies(); while (notifies.hasNext()) { Notification notification = notifies.next(); ServletOutputStream outputStream = response.getOutputStream(); outputStream.write(("event: " + notification.getEvent() + "\n").getBytes()); outputStream.write(("data: " + notification.getData() + "\n\n").getBytes()); outputStream.flush(); } } catch (Exception error) { log.error("error notification/stream : " + error.getMessage()); } log.info("out notification/stream”); } String obtainUsername(HttpServletRequest request) { return request.getParameter("username"); }}

'

class FeedService { final String FEED_NOTIFY_URL = "http://localhost:9000/user/%s/feed"; final ObjectMapper MAPPER = new ObjectMapper(); FeedNotify getFeedNotify(String username) { try { URL url = new URL(String.format(FEED_NOTIFY_URL, username)); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("GET"); urlConnection.setRequestProperty("Accept", "application/json"); urlConnection.setConnectTimeout(3000); urlConnection.setReadTimeout(3000); return MAPPER.readValue(urlConnection.getInputStream(), FeedNotify.class); } catch (IOException error) { throw new ServiceOperationException(error); } }}

// FeedService , FeedService feedService = new FeedService();String username = "guest"; Iterator<Notification> feedNotifies = Stream.generate(() -> feedService.getFeedNotify(username)) .map(Notification::of) .iterator();while (feedNotifies.hasNext()) { Notification next = feedNotifies.next(); System.out.println(next);}

'

'

'

!

#!

)

%

"$

)

"$

'

!

!

!

%%$

(

'

'

'

!

!

!

%%$

(!

'

'

@WebServlet(urlPatterns = "/async", asyncSupported = true) public class SimpleAsyncServlet extends HttpServlet { protected void service(HttpServletRequest request, HttpServletResponse response) throws Exception { // AsyncContext asyncContext = request.startAsync(request, response); // asyncContext.setTimeout(60 * 1000); // asyncContext.addListener(new AsyncListener() { // public void onComplete(AsyncEvent event) throws IOException { … } // AsyncContext.setTimeout() public void onTimeout(AsyncEvent event) throws IOException { … } // public void onError(AsyncEvent event) throws IOException { … } // public void onStartAsync(AsyncEvent event) throws IOException { … } }); // asyncContext.start(new Runnable() { public void run() { // asyncContext.complete(); // } }); }}

'

@WebServlet(urlPatterns = "/notification/stream/async", asyncSupported = true) public class AsyncNotificationStreamServlet extends HttpServlet { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws Exception { log.info("in notification/stream/async"); response.setCharacterEncoding("utf-8"); response.setContentType("text/event-stream"); AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(30000); asyncContext.addListener(new AsyncListener() { public void onComplete(AsyncEvent event) throws IOException { … } public void onTimeout(AsyncEvent event) throws IOException { … } public void onError(AsyncEvent event) throws IOException { … } public void onStartAsync(AsyncEvent event) throws IOException { … } }); asyncContext.start(() -> { try { NotificationStream notificationStream = new NotificationStream(obtainUsername(request)); Iterator<Notification> notifies = notificationStream.feedNotifies(); while (notifies.hasNext() && !asyncDone.get()) { Notification notification = notifies.next(); ServletOutputStream outputStream = asyncContext.getResponse().getOutputStream(); outputStream.write(("event: " + notification.getEvent() + "\n").getBytes()); outputStream.write(("data: " + notification.getData() + "\n\n").getBytes()); outputStream.flush(); } } catch (Exception error) { log.error("error notification/stream/async - " + error.getMessage()); } finally { asyncContext.complete(); } }); log.info("out notification/stream/async"); } }

'

'

'

!"

#$

'

#

#

#

!!!!!!

'

'

class NotifyApi { static Log LOG = LogFactory.getLog(NotifyApi.class); final String FEED_NOTIFY_URL = "http://localhost:9000/user/%s/feed"; final String FRIEND_NOTIFY_URL = "http://localhost:9000/user/%s/friend-recommendation"; final ObjectMapper MAPPER = new ObjectMapper(); FeedNotify getFeedNotify(String username) { LOG.info("FeedNotify ."); try { URL url = new URL(String.format(FEED_NOTIFY_URL, username)); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); FeedNotify feedNotify = MAPPER.readValue(urlConnection.getInputStream(), FeedNotify.class); LOG.info("FeedNotify ."); return feedNotify; } catch (IOException error) { throw new ServiceOperationException(error); } } FriendRecommendationNotify getFriendRecommendationNotify(String username) { LOG.info("FriendRecommendationNotify ."); try { URL url = new URL(String.format(FRIEND_NOTIFY_URL, username));

// LOG.info("FriendRecommendationNotify ."); return notify; } catch (IOException error) { throw new ServiceOperationException(error); } }}

'

public class SynchronousCallExample { public static void main(String[] args) { NotifyApi notifyApi = new NotifyApi(); String username = "guest"; FeedNotify feedNotify = notifyApi.getFeedNotify(username); // feedNotify FriendRecommendationNotify friendNotify = notifyApi.getFriendRecommendationNotify(username); // friendNotify

} }

'

'

FeedNotify

getFeedNotify(username)

HTTP /

FriendRecommendationNotify

getFriendRecommendationNotify(username)

HTTP /

'

'

public class AsynchronousCallByThreadExample { static Log LOG = LogFactory.getLog(AsynchronousCallByThreadExample.class); public static void main(String[] args) throws Exception { NotifyApi notifyApi = new NotifyApi(); String username = "guest";

Thread feedThread = new Thread(new Runnable() { @Override public void run() { FeedNotify feedNotify = notifyApi.getFeedNotify(username); LOG.info(feedNotify); } }); feedThread.start();

Thread friendThread = new Thread(() -> { FriendRecommendationNotify friendNotify = notifyApi.getFriendRecommendationNotify(username); LOG.info(friendNotify); }); friendThread.start(); }}

'

'

)

'

<<Interface>>

Executor

<<Interface>>

ExecutorService

<<Interface>>

ScheduledExecutorService

ForkJoinPool

AbstractExecutorService

ThreadPoolExecutor

ScheduledThreadPoolExecutor

ExecutorService executorService = Executors.newFixedThreadPool(4);

'

public class AsynchronousCallByExecutorExample { static Log LOG = LogFactory.getLog(AsynchronousCallByExecutorExample.class); public static void main(String[] args) throws Exception { NotifyApi notifyApi = new NotifyApi(); String username = "guest"; Executor executor = Executors.newFixedThreadPool(4); executor.execute(new Runnable() { @Override public void run() { FeedNotify feedNotify = notifyApi.getFeedNotify(username); LOG.info(feedNotify); } });

executor.execute(() -> { FriendRecommendationNotify friendNotify = notifyApi.getFriendRecommendationNotify(username); LOG.info(friendNotify); }); } }

'

'

ExecutorService executorService = Executors.newCachedThreadPool();

Future<String> future = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { // return "asynchronous call"; } });

String result = future.get(1, TimeUnit.SECONDS);

'

class NotifyApi { static Log LOG = LogFactory.getLog(NotifyApi.class); final String FEED_NOTIFY_URL = "http://localhost:9000/user/%s/feed"; final String FRIEND_NOTIFY_URL = "http://localhost:9000/user/%s/friend-recommendation"; final ExecutorService executorService = Executors.newCachedThreadPool(); final ObjectMapper objectMapper = new ObjectMapper();

FeedNotify getFeedNotify(String username) { … } Future<FeedNotify> getFeedNotifyForFuture(String username) { return executorService.submit(new Callable<FeedNotify>() { @Override public FeedNotify call() throws Exception { return getFeedNotify(username); } }); }

FriendRecommendationNotify getFriendRecommendationNotify(String username) { … } Future<FriendRecommendationNotify> getFriendRecommendationNotifyForFuture(String username) { return executorService.submit(() -> getFriendRecommendationNotify(username)); }}

'

NotifyApi notifyApi = new NotifyApi();String username = "guest"; Future<FeedNotify> feedFuture = notifyApi.getFeedNotifyForFuture(username);Future<FriendRecommendationNotify> friendFuture = notifyApi.getFriendRecommendationNotifyForFuture(username);

for (;;) { if (feedFuture.isDone()) { FeedNotify feedNotify = feedFuture.get(); // feedNotify break; }

// // feedFuture.cancel(true); LOG.info("FeedNotify ."); Thread.sleep(100);}

FriendRecommendationNotify friendNotify = friendFuture.get(1, TimeUnit.SECONDS); // friendNotify

'

'

getFeedNotify(username)

HTTP /

feedFuture

HTTP /

friendFuture

getFriendRecommendationNotify(username)

feedFuture.isDone() or cancel()

feedFuture.get()

friendFuture.get()

'

NotifyApi notifyApi = new NotifyApi();String username = "elton";

Future<User> userFuture = notifyApi.getUserForFuture(username);for (;;) { if (userFuture.isDone()) { User user = userFuture.get(); FeedNotify feedNotify = notifyApi.getFeedNotifyForFuture(user) .get(1, TimeUnit.SECONDS); // break; } LOG.info("User ."); Thread.sleep(100);}

'

NotifyApi notifyApi = new NotifyApi();String username = "guest";

notifyApi.getFeedNotify(username, new CompletionHandler<FeedNotify>() { @Override public void onSuccess(FeedNotify result) { // } @Override public void onFailure(Throwable ex) { // } });

'

class NotifyApi {

// interface CompletionHandler<R> extends SuccessCallback<R>, FailureCallback {

}

// interface SuccessCallback<R> { void onSuccess(R result); }

// interface FailureCallback { void onFailure(Throwable ex); }

}

'

class NotifyApi { static Log LOG = LogFactory.getLog(NotifyApi.class); final String FEED_NOTIFY_URL = "http://localhost:9000/user/%s/feed"; final String FRIEND_NOTIFY_URL = "http://localhost:9000/user/%s/friend-recommendation"; final ExecutorService executorService = Executors.newCachedThreadPool(); final ObjectMapper objectMapper = new ObjectMapper(); FeedNotify getFeedNotify(String username) { … } void getFeedNotify(String username, CompletionHandler<FeedNotify> completionHandler) { executorService.execute(new Runnable() { @Override public void run() { try { completionHandler.onSuccess(getFeedNotify(username)); } catch (Exception error) { completionHandler.onFailure(error); } } }); }

FriendRecommendationNotify getFriendRecommendationNotify(String username) { … } void getFriendRecommendationNotify(String username, CompletionHandler<FriendRecommendationNotify> handler) { … }}

'

NotifyApi notifyApi = new NotifyApi();String username = "guest"; notifyApi.getFeedNotify(username, new CompletionHandler<FeedNotify>() { @Override public void onSuccess(FeedNotify result) { // } @Override public void onFailure(Throwable ex) { // } });notifyApi.getFriendRecommendationNotify(username, new CompletionHandler<FriendRecommendationNotify>() { @Override public void onSuccess(FriendRecommendationNotify result) { } @Override public void onFailure(Throwable ex) { } });

'

'

execute callback

getFeedNotify(username)

HTTP /

execute callback

getFriendRecommendationNotify(username)

HTTP /

'

NotifyApi notifyApi = new NotifyApi();String username = "elton";

notifyApi.getUser(username, new NotifyApi.CompletionHandler<User>() { @Override public void onSuccess(User user) {

notifyApi.getFeedNotify(user, new NotifyApi.CompletionHandler<FeedNotify>() { @Override public void onSuccess(FeedNotify feedNotify) { // } @Override public void onFailure(Throwable error) { // } });

} @Override public void onFailure(Throwable error) { // } });

'

CompletableFuture.supplyAsync(() -> { // return "async task"; }).thenApply(result -> { // return result.length();}).exceptionally(error -> { // return Integer.MIN_VALUE; }).thenAccept(length -> { // LOG.info("Length : " + length);});

'

class NotifyApi { static Log LOG = LogFactory.getLog(NotifyApi.class); final String FEED_NOTIFY_URL = "http://localhost:9000/user/%s/feed"; final String FRIEND_NOTIFY_URL = "http://localhost:9000/user/%s/friend-recommendation"; final ExecutorService executorService = Executors.newCachedThreadPool(); final ObjectMapper objectMapper = new ObjectMapper();

FeedNotify getFeedNotify(String username) { … } CompletableFuture<FeedNotify> getFeedNotifyForCF(String username) { return CompletableFuture.supplyAsync(new Supplier<FeedNotify>() { @Override public FeedNotify get() { return getFeedNotify(username); } }, executorService); }

FriendRecommendationNotify getFriendRecommendationNotify(String username) { … } CompletableFuture<FriendRecommendationNotify> getFriendRecommendationNotifyForCF(String username) { return CompletableFuture.supplyAsync(() -> getFriendRecommendationNotify(username), executorService); }}

'

NotifyApi notifyApi = new NotifyApi();String username = "guest"; notifyApi.getFeedNotifyForCF(username).thenAccept(feedNotify -> { // feedNotify });

notifyApi.getFriendRecommendationNotifyForCF(username).thenAccept(friendNotify -> { // friendNotify });// 2 notifyApi.getUserForCF(username) .thenCompose(notifyApi::getFeedNotifyForCF) .thenAccept(notifications -> { // });

'

'

)

%

%

%

"

'

class NotificationStreamObservable { ExecutorService executorService = Executors.newFixedThreadPool(1); Future<?> future = new FutureTask<>(() -> {}, null); NotifyApi notifyApi = new NotifyApi(); String username; List<NotificationStreamObserver> observers = new CopyOnWriteArrayList<>(); NotificationStreamObservable(String username) { this.username = username; } void register(NotificationStreamObserver observer) { observers.add(observer); } void unregister(NotificationStreamObserver observer) { observers.remove(observer); } void subscribe() { future = executorService.submit(() -> { boolean running = true; while (running) { Notification feedNotify = Notification.of(notifyApi.getFeedNotify(username)); observers.forEach(observer -> observer.onNotification(feedNotify));

// } }); } void unsubscribe() { future.cancel(true); observers.clear(); }}

'

interface NotificationStreamObserver { void onNotification(Notification notification);}

String username = "guest"; NotificationStreamObservable observable = new NotificationStreamObservable(username);observable.register(notification -> { // });observable.subscribe();// , // observable.unsubscribe();

'

'

'

@WebServlet(urlPatterns = "/notification/stream/async", asyncSupported = true) public class AsyncNotificationStreamServlet extends HttpServlet {

@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws Exception { log.info("in notification/stream/async"); response.setCharacterEncoding("utf-8"); response.setContentType("text/event-stream"); AtomicBoolean asyncDone = new AtomicBoolean(false); AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout((new Random().nextInt(5) + 5) * 1000); asyncContext.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { } @Override public void onTimeout(AsyncEvent event) throws IOException { } @Override public void onError(AsyncEvent event) throws IOException { } @Override public void onStartAsync(AsyncEvent event) throws IOException { } }); asyncContext.start(() -> {

// }); log.info("out notification/stream/async"); }

}

'

@WebServlet(urlPatterns = "/notification/stream/async", asyncSupported = true) public class AsyncNotificationStreamServlet extends HttpServlet {

@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws Exception { log.info("in notification/stream/async"); response.setCharacterEncoding("utf-8"); response.setContentType("text/event-stream"); AtomicBoolean asyncDone = new AtomicBoolean(false); AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout((new Random().nextInt(5) + 5) * 1000); asyncContext.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { } @Override public void onTimeout(AsyncEvent event) throws IOException { } @Override public void onError(AsyncEvent event) throws IOException { } @Override public void onStartAsync(AsyncEvent event) throws IOException { } }); asyncContext.start(() -> {

// }); log.info("out notification/stream/async"); }

}

'

!#

$

#$

$

$

%

'

public class NotificationStreamObservable extends Observable { final static ExecutorService executorService = Executors.newCachedThreadPool(); Iterator<Notification> feedNotifies; Future<?> feedFuture; public NotificationStreamObservable(NotificationStream stream) { this.feedNotifies = Objects.requireNonNull(stream.feedNotifies()); } public void subscribe() { feedFuture = executorService.submit(() -> { boolean running = true; while (running) { if (countObservers() > 0) { try { Notification notification = feedNotifies.next(); setChanged(); notifyObservers(notification); } catch (Exception error) { running = false; setChanged(); notifyObservers(error); } } if (Thread.interrupted()) { running = false; } Thread.yield(); } }); } public void unsubscribe() { feedFuture.cancel(true); deleteObservers(); } }

'

class NotificationStreamObserver implements Observer, AsyncListener { final AsyncContext asyncContext; public NotificationStreamObserver(AsyncContext asyncContext) { this.asyncContext = Objects.requireNonNull(asyncContext); } @Override public void update(Observable observable, Object event) { if (event instanceof Notification) { handler((Notification) event); } // if (event instanceof Throwable) { handlerError(observable, (Throwable) event); } } @Override public void onComplete(AsyncEvent event) throws IOException { log.info("complete notification/stream/async-observer"); } @Override public void onTimeout(AsyncEvent event) throws IOException { handlerError(new TimeoutException("timeout")); } @Override public void onError(AsyncEvent event) throws IOException { handlerError(event.getThrowable()); } @Override public void onStartAsync(AsyncEvent event) throws IOException { }

// handle, handleError }

'

@WebServlet(urlPatterns = "/notification/stream/observer", asyncSupported = true) public class ObserverNotificationStreamServlet extends HttpServlet { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws Exception { log.info("in notification/stream/observer"); response.setCharacterEncoding("utf-8"); response.setContentType("text/event-stream"); AsyncContext asyncContext = request.startAsync(request, response); asyncContext.setTimeout((new Random().nextInt(5) + 5) * 1000);

NotificationStream notificationStream = new NotificationStream(obtainUsername(request)); NotificationStreamObservable streamObservable = new NotificationStreamObservable(notificationStream); NotificationStreamObserver streamObserver = new NotificationStreamObserver(asyncContext); streamObservable.addObserver(streamObserver); asyncContext.addListener(streamObserver);

streamObservable.subscribe(); log.info("out notification/stream/observer"); } String obtainUsername(HttpServletRequest request) { String username = request.getParameter("username"); if (StringUtils.hasText(username)) { return username; } return "anonymous"; } }

'

'

'

!"

#$

#

#

#

$

$

$

'

public class NotificationStreamObservable extends Observable { final static ExecutorService executorService = Executors.newCachedThreadPool(); Iterator<Notification> feedNotifies; Future<?> feedFuture; public NotificationStreamObservable(NotificationStream stream) { this.feedNotifies = requireNonNull(stream).feedNotifies(); } public void subscribe() { feedFuture = executorService.submit(() -> { boolean running = true; while (running) { if (countObservers() > 0) { try { Notification notification = feedNotifies.next(); setChanged(); notifyObservers(notification); } catch (Exception error) { running = false; setChanged(); notifyObservers(error); } } // } }); } // }

'

public class NotificationStreamObservable extends Observable { final static ExecutorService executorService = Executors.newCachedThreadPool(); Iterator<Notification> feedNotifies; Future<?> feedFuture;

Iterator<Notification> friendNotifies; Future<?> friendFuture; public NotificationStreamObservable(NotificationStream stream) { this.feedNotifies = requireNonNull(stream).feedNotifies(); this.friendNotifies = requireNonNull(stream).friendRecommendationNotifies(); } public void subscribe() { feedFuture = executorService.submit(() -> { boolean running = true; while (running) { if (countObservers() > 0) { try { Notification feedNotify = feedNotifies.next(); setChanged(); notifyObservers(feedNotify);

Notification friendNotify = friendNotifies.next(); setChanged(); notifyObservers(friendNotify); } catch (Exception error) { running = false; setChanged(); notifyObservers(error); } } // } }); } // }

'

feedNotifies.next()

friendNotifies.next()

feedNotify

friendNotify

'

class NotificationPublisher implements Runnable { final Iterator<Notification> notifies; final AtomicBoolean running = new AtomicBoolean(true); NotificationPublisher(Iterator<Notification> notifies) { this.notifies = notifies; } @Override public void run() { while (running.get()) { if (countObservers() > 0) { try { Notification notification = notifies.next(); setChanged(); notifyObservers(notification); } catch (Exception error) { cancel(); setChanged(); notifyObservers(error); } } if (Thread.interrupted()) { cancel(); } Thread.yield(); } } void cancel() { running.set(false); }}

'

class MultipleNotificationStreamObservable extends Observable { final static ExecutorService executor = Executors.newCachedThreadPool(); NotificationPublisher feedPublisher; NotificationPublisher friendPublisher; public MultipleNotificationStreamObservable(NotificationStream stream) { this.feedPublisher = new NotificationPublisher(requireNonNull(stream).feedNotifies()); this.friendPublisher = new NotificationPublisher(requireNonNull(stream).friendRecommendationNotifies()); } public void subscribe() { executor.execute(feedPublisher); executor.execute(friendPublisher); } public void unsubscribe() { feedPublisher.cancel(); friendPublisher.cancel(); deleteObservers(); }}

'

'

!

'

!

'

class MultipleNotificationStreamObserver implements Observer, AsyncListener { @Override public void update(Observable observable, Object event) { synchronized (this) { if (event instanceof Notification) { handler((Notification) event); } // if (event instanceof Throwable) { handlerError((Throwable) event); } } } // }

'

class MultipleNotificationStreamObserver implements Observer, AsyncListener { ReentrantLock reentrantLock = new ReentrantLock(); @Override public void update(Observable observable, Object event) { reentrantLock.lock(); if (event instanceof Notification) { handler((Notification) event); } // if (event instanceof Throwable) { handlerError((Throwable) event); } reentrantLock.unlock(); } // }

'

!

'

BlockingQueue<Object> notifyQueue = new LinkedBlockingQueue<>();

class SequentialNotifyObserversPublisher implements Runnable { final Iterator<Notification> notifies; final AtomicBoolean running = new AtomicBoolean(true); SequentialNotifyObserversRunnable(Iterator<Notification> notifies) { this.notifies = notifies; } @Override public void run() { while (running.get()) { if (countObservers() > 0) { try { notifyQueue.put(notifies.next()); } catch (Exception error) { cancel(); try { notifyQueue.put(error); } catch (InterruptedException ignore) { } } } if (Thread.interrupted()) { cancel(); } Thread.yield(); } } void cancel() { running.set(false); }}

'

class MultipleNotificationStreamObservable extends Observable { final static ExecutorService executor = Executors.newFixedThreadPool(3); BlockingQueue<Object> notifyQueue = new LinkedBlockingQueue<>(); Future<?> notifyFuture; SequentialNotifyObserversPublisher sequentialFeed; SequentialNotifyObserversPublisher sequentialFriend; public MultipleNotificationStreamObservable(NotificationStream stream) { this.sequentialFeed = new SequentialNotifyObserversRunnable(stream.feedNotifies()); this.sequentialFriend = new SequentialNotifyObserversRunnable(stream.friendRecommendationNotifies()); } public void subscribe() { executor.execute(sequentialFeed); executor.execute(sequentialFriend); notifyFuture = executor.submit(() -> { boolean running = true; while (running) { if (countObservers() > 0) { try { Object event = notifyQueue.take(); setChanged(); notifyObservers(event); } catch (InterruptedException e) { running = false; } } if (Thread.interrupted()) { running = false; } Thread.yield(); } }); } // }

'

'

'

'

'

void printUserInfo(String username) { NotifyApi notifyApi = new NotifyApi(); NotifyApi.User user = notifyApi.getUser(username); Long feedCount = notifyApi.getFeedCount(username); Long friendCount = notifyApi.getFriendCount(username); UserInfo userInfo = new UserInfo(user.getName(), feedCount, friendCount); System.out.println("User Info"); System.out.println("name = " + userInfo.getName()); System.out.println("feedCount = " + userInfo.getFeedCount()); System.out.println("friendCount = " + userInfo.getFriendCount());}

printUserInfo("fay"); // 1printUserInfo("murphy"); // 2printUserInfo("nichole"); // 3

'

void printUserInfo(String username) { … }

ExecutorService executorService = Executors.newFixedThreadPool(3); executorService.execute(() -> printUserInfo("fay")); // 1 executorService.execute(() -> printUserInfo("murphy")); // 2 executorService.execute(() -> printUserInfo("nichole")); // 3

'

void printUserInfo(String username) { … }

ExecutorService executorService = Executors.newFixedThreadPool(3); executorService.execute(() -> printUserInfo("fay")); // 1 executorService.execute(() -> printUserInfo("murphy")); // 2 executorService.execute(() -> printUserInfo("nichole")); // 3 executorService.execute(() -> printUserInfo("phillip")); // 4 executorService.execute(() -> printUserInfo("adrienne")); // 5 executorService.execute(() -> printUserInfo("nita")); // 6

'

ExecutorService executorService = Executors.newFixedThreadPool(1); void printUserInfo(String username) { CompletableFuture.supplyAsync(() -> notifyApi.getUser(username), executorService) .thenCompose(user -> { CompletableFuture<Long> feedCountCF = CompletableFuture.supplyAsync( () -> notifyApi.getFeedCount(username), executorService); CompletableFuture<Long> friendCountCF = CompletableFuture.supplyAsync( () -> notifyApi.getFriendCount(username), executorService); return feedCountCF.thenCombineAsync(friendCountCF, (feedCount, friendCount) -> { return new UserInfo(user.getName(), feedCount, friendCount); }, executorService); }) .thenAccept(userInfo -> { System.out.println("User Info"); System.out.println("name = " + userInfo.getName()); System.out.println("feedCount = " + userInfo.getFeedCount()); System.out.println("friendCount = " + userInfo.getFriendCount()); });}

printUserInfo("fay"); // 1printUserInfo("murphy"); // 2printUserInfo("nichole"); // 3

'

'

ExecutorService executorService = Executors.newFixedThreadPool(3); void printUserInfo(String username) {

// } printUserInfo("fay"); // 1printUserInfo("murphy"); // 2printUserInfo("nichole"); // 3printUserInfo("phillip"); // 4printUserInfo("adrienne"); // 5printUserInfo("nita"); // 6

'

class NotificationPublisher implements Runnable { Iterator<Notification> notifies; NotifyObserversRunnable(Iterator<Notification> notifies) { this.notifies = notifies; } @Override public void run() { boolean running = true; while (running) { if (countObservers() > 0) { try { Notification notification = notifies.next(); setChanged(); notifyObservers(notification); } catch (Exception error) { running = false; setChanged(); notifyObservers(error); } } if (Thread.interrupted()) { running = false; } Thread.yield(); } }}

'

class NotificationPublisher implements Runnable { final Iterator<Notification> notifies; final AtomicBoolean running = new AtomicBoolean(true); NotifyObserversRunnable(Iterator<Notification> notifies) { this.notifies = notifies; } public void run() { if (countObservers() > 0) { CompletableFuture<Object> completableFuture = new CompletableFuture<>(); completableFuture.thenAcceptAsync(event -> { if (running.get()) { setChanged(); notifyObservers(event); } schedule(20); }, notifyExecutor).exceptionally(throwable -> { cancel(); setChanged(); notifyObservers(throwable); return null; }); try { completableFuture.complete(notifies.next()); } catch (Exception error) { completableFuture.completeExceptionally(error); } } schedule(50); } void schedule(long millis) { if (running.get()) { serviceExecutor.schedule(this, millis, TimeUnit.MILLISECONDS); } } void cancel() { running.set(false); }}

'

class AsyncMultipleNotificationStreamObservable extends Observable { final static ScheduledExecutorService serviceExecutor = Executors.newScheduledThreadPool(2); final static ScheduledExecutorService notifyExecutor = Executors.newScheduledThreadPool(1); NotifyObserversRunnable feedPublisher; NotifyObserversRunnable friendPublisher; public AsyncMultipleNotificationStreamObservable(NotificationStream stream) { this.feedPublisher = new NotifyObserversRunnable(requireNonNull(stream).feedNotifies()); this.friendPublisher = new NotifyObserversRunnable(requireNonNull(stream).friendRecommendationNotifies()); } public void subscribe() { serviceExecutor.schedule(feedPublisher, 10, TimeUnit.MILLISECONDS); serviceExecutor.schedule(friendPublisher, 10, TimeUnit.MILLISECONDS); } public void unsubscribe() { feedPublisher.cancel(); friendPublisher.cancel(); }

}

'

'

'

'

'

SocketChannel channel = SocketChannel.open();channel.configureBlocking(true);

Socket socket = channel.socket();socket.connect(new InetSocketAddress("www.naver.com", 80));BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));PrintStream output = new PrintStream(socket.getOutputStream());output.println("GET / HTTP/1.0");output.println();StringBuilder response = new StringBuilder();String line = input.readLine();while (Objects.nonNull(line)) { response.append(line).append("\n"); line = input.readLine(); // body if ("".equals(line)) { while (Objects.nonNull(line)) { line = input.readLine(); } }} input.close();output.close();

'

'

'

!

!

!

!

)

PollSelectorImpl

WindowsSelectorImpl

KQueueSelectorImpl

EPollSelectorImpl

'

Selector selector = Selector.open();SocketChannel socketChannel = SocketChannel.open();socketChannel.configureBlocking(false);socketChannel.connect(new InetSocketAddress("www.naver.com", 80));socketChannel.register(selector, SelectionKey.OP_CONNECT);while (socketChannel.isOpen()) { if (selector.select() > 0) { Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); if (selectionKey.isConnectable()) { SocketChannel channel = (SocketChannel) selectionKey.channel(); // } else if (selectionKey.isWritable()) { SocketChannel channel = (SocketChannel) selectionKey.channel(); // } else if (selectionKey.isReadable()) { SocketChannel channel = (SocketChannel) selectionKey.channel(); // }

// iterator.remove(); } }}

'

'

AsynchronousChannelGroup asynchronousChannelGroup = AsynchronousChannelGroup.withFixedThreadPool(1); AsynchronousSocketChannel asynchronousSocketChannel = AsynchronousSocketChannel.open(asynchronousChannelGroup);

SocketAddress socketAddress = new InetSocketAddress(“www.naver.com”, 80); asynchronousSocketChannel.connect(socketAddress, null, new CompletionHandler<Void, Void>() { @Override public void completed(Void v, Void attachment) {

// asynchronousSocketChannel.write(writeBuffer, null, new CompletionHandler<Integer, Void>() { @Override public void completed(Integer length, Void attachment) {

// asynchronousSocketChannel.read(readBuffer, null, new CompletionHandler<Integer, Void>() { @Override public void completed(Integer length, Void attachment) { // } @Override public void failed(Throwable error, Void attachment) { // } }); } @Override public void failed(Throwable error, Void attachment) { // } }); } @Override public void failed(Throwable exc, Void attachment) { // }});

'

'

'

✔ �Netty(ht

tp://netty.io

)

✔ �gRPG(htt

p://www.g

rpc.io)

✔ �AsyncHtt

pClient(http

s://hc.apac

he.org/)

'

public FriendRecommendationNotify getRecommendationNotify(String username) throws ServiceOperationException { try { HttpGet request = new HttpGet(String.format(url, username, processing.name())); HttpResponse response = httpClient.execute(request); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new ServiceOperationException(); } return objectMapper.readValue(response.getEntity().getContent(), FriendRecommendationNotify.class); } catch (IOException error) { throw new ServiceOperationException(error); }}

'

public CompletableFuture<FriendRecommendationNotify> getRecommendationNotifyAsync(String username) { CompletableFuture<FriendRecommendationNotify> completableFuture = new CompletableFuture<>(); HttpGet request = new HttpGet(String.format(url, username, processing.name())); httpAsyncClient.execute(request, new FutureCallback<HttpResponse>() { @Override public void completed(HttpResponse response) { try { int statusCode = response.getStatusLine().getStatusCode(); InputStream content = response.getEntity().getContent(); if (statusCode != HttpStatus.SC_OK) { completableFuture.completeExceptionally(new ServiceOperationException()); } completableFuture.complete(objectMapper.readValue(content, FriendRecommendationNotify.class)); } catch (IOException error) { completableFuture.completeExceptionally(new ServiceOperationException(error)); } } @Override public void failed(Exception error) { completableFuture.completeExceptionally(error); } @Override public void cancelled() { completableFuture.cancel(true); } }); return completableFuture;}

'

@Testpublic void getRecommendationNotify() throws Exception { StopWatch stopWatch = new StopWatch("recommendationNotify"); stopWatch.start(); IntStream.rangeClosed(1, 10) .mapToObj(value -> friendService.getRecommendationNotify("user-" + value)) .forEach(System.out::println); stopWatch.stop(); System.out.println(); System.out.println(stopWatch.prettyPrint());}

@Testpublic void getRecommendationNotifyAsync() throws Exception { StopWatch stopWatch = new StopWatch("recommendationNotifyAsync"); stopWatch.start(); IntStream.rangeClosed(1, 10) .mapToObj(value -> friendService.getRecommendationNotifyAsync("user-" + value)) .collect(Collectors.toList()) .stream() .map(CompletableFuture::join) .forEach(System.out::println); stopWatch.stop(); System.out.println(); System.out.println(stopWatch.prettyPrint());}

'

class NotificationPublisher implements Runnable { final Supplier<CompletableFuture<Notification>> notifies; final AtomicBoolean running = new AtomicBoolean(true); NotifyObserversRunnable(Supplier<CompletableFuture<Notification>> notifies) { this.notifies = notifies; } @Override public void run() { if (countObservers() > 0) { notifies.get().thenAcceptAsync(event -> { if (running.get()) { setChanged(); notifyObservers(event); } schedule(20); }, notifyExecutor).exceptionally(throwable -> { cancel(); setChanged(); notifyObservers(throwable); return null; }); } else { schedule(50); } } void schedule(long millis) { if (running.get()) { serviceExecutor.schedule(this, millis, TimeUnit.MILLISECONDS); } } void cancel() { running.set(false); }}

'

class NonBlockingAsyncMultipleNotificationStreamObservable extends Observable { final static ScheduledExecutorService serviceExecutor = Executors.newScheduledThreadPool(1); final static ScheduledExecutorService notifyExecutor = Executors.newScheduledThreadPool(1); NotifyObserversRunnable feed; NotifyObserversRunnable friend; public NonBlockingAsyncMultipleNotificationStreamObservable(NotificationStream stream) { requireNonNull(stream); this.feed = new NotifyObserversRunnable(stream::feedNotifyAsync); this.friend = new NotifyObserversRunnable(stream::friendRecommendationNotifyAsync); } public void subscribe() { serviceExecutor.execute(feed); serviceExecutor.execute(friend); } public void unsubscribe() { if (nonNull(feed)) feed.cancel(); if (nonNull(friend)) friend.cancel(); deleteObservers(); }}

'

'

'

class NonBlockingAsyncMultipleNotificationStreamObserver implements Observer, AsyncListener { final NonBlockingAsyncMultipleNotificationStreamObservable streamObservable; final AsyncContext asyncContext; public NonBlockingAsyncMultipleNotificationStreamObserver(N…Observable streamObservable, AsyncContext asyncContext) { this.streamObservable = Objects.requireNonNull(streamObservable); this.asyncContext = Objects.requireNonNull(asyncContext); } @Override public void update(Observable observable, Object event) { if (event instanceof Notification) { handler((Notification) event); } if (event instanceof Throwable) { handlerError((Throwable) event); } } private void handler(Notification notification) { try { log.info("revised notification/stream/non-blocking-async-concurrency : " + notification.getEvent()); ServletOutputStream outputStream = asyncContext.getResponse().getOutputStream(); outputStream.write(("event: " + notification.getEvent() + "\n").getBytes()); outputStream.write(("data: " + notification.getData() + "\n\n").getBytes()); outputStream.flush(); } catch (IOException error) { throw new DataWriteException(error); } } private void handlerError(Throwable error) { log.error("error notification/stream/non-blocking-async-concurrency : " + error.getMessage()); streamObservable.unsubscribe(); asyncContext.complete(); }}

'

✔�비봉쇄�입/출력을�위한�새로운�인터페이스�•ReadListener�-�사용�가능한�데이터를�읽을�수�있는�방법�

•WriteListener�-�데이터�쓰기가�가능한�때를�알�수�있는�방법�

✔�ServletInputStream�에�추가된�기능�•isFinished()�

•isReady()�

•setReadListener()�

✔�ServletOutputStream�에�추가된�기능�•isReady()�

•setWriterListener()

'

ServletInputStream servletInputStream = request.getInputStream();servletInputStream.setReadListener(new ReadListener() { @Override public void onDataAvailable() throws IOException { // } @Override public void onAllDataRead() throws IOException { // } @Override public void onError(Throwable throwable) { // }});

ServletOutputStream servletOutputStream = response.getOutputStream();servletOutputStream.setWriteListener(new WriteListener() { @Override public void onWritePossible() throws IOException { // } @Override public void onError(Throwable throwable) { // }});

'

class NonBlockingAsyncMultipleNotificationStreamObserver implements Observer, AsyncListener, WriteListener { final NonBlockingAsyncMultipleNotificationStreamObservable streamObservable; final AsyncContext asyncContext; final ServletOutputStream outputStream; final AtomicInteger counter = new AtomicInteger(1); public NonBlockingAsyncMultipleNotificationStreamObserver(N…Observable streamObservable, AsyncContext asyncContext) { this.streamObservable = Objects.requireNonNull(streamObservable); this.asyncContext = Objects.requireNonNull(asyncContext); this.outputStream = asyncContext.getResponse().getOutputStream(); }

// @Override public void onWritePossible() throws IOException { // ignore } @Override public void onError(Throwable throwable) { handlerError(throwable); } private void handler(Notification notification) { String content = notification.toContent(); ioExecutor.schedule(() -> write(content.getBytes()), 10, TimeUnit.MILLISECONDS); }

private void handlerError(Throwable error) { log.error("error notification/stream/non-blocking-async-concurrency : " + error.getMessage()); streamObservable.unsubscribe(); asyncContext.complete(); }

// …

'

//

final static ScheduledExecutorService ioExecutor = Executors.newScheduledThreadPool(1);

private void write(byte[] data) { if (outputStream.isReady()) { try { outputStream.write(data); ioExecutor.schedule(this::flush, 10, TimeUnit.MILLISECONDS); } catch (IOException error) { handlerError(error); } } else { ioExecutor.schedule(() -> write(data), 10, TimeUnit.MILLISECONDS); } } private void flush() { if (outputStream.isReady()) { try { outputStream.flush(); } catch (IOException error) { handlerError(error); } } else { ioExecutor.schedule(this::flush, 10, TimeUnit.MILLISECONDS); } }}

'

!"

#$

#

#

#

$

'

'

Recommended