非同期連続処理のイディオム
(続々)Webスレッドをタイムアウトさせる方法で紹介したが、タイムアウトさせる処理は元より、Javaにおける連続した非同期処理を簡易に実現できる良いイディオムのような気がしてきた。
例えばサーブレットにおける、URL毎の応答時間を計測する処理を書きたいとする。一番簡単なのはサーブレットフィルタで実装することだが、応答時間の記録に時間がかかるようだと、Webスレッドの処理に影響が出る可能性があるが、以下のように一連のフィルタチェインに対して、非同期で応答時間の記録ができる。
public class TimeCountFilter implements Filter { private static Timer timer = new Timer(); private static Map histries = new HashMap(); public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //フィルタチェインの前後の時間を計測して、応答時間とする long before = System.currentTimeMillis(); chain.doFilter(request, response); long after = System.currentTimeMillis(); if (request instanceof HttpServletRequest){ final String uri; uri = ( (HttpServletRequest)request ).getRequestURI(); final long time = after - before; //タイマータスクをスケジューリング timer.schedule(new TimerTask(){ public void run() { //synchronized(histries) { try{ if ( histries.containsKey(uri)) { List list = (List)histries.get(uri); list.add(new Long(time)); //履歴は100件を越えたら順に消していこう if ( list.length > 100 ) list.remove(0); } else { List list = new ArrayList(); list.add(new Long(time)); histries.put(uri, list); } }catch(Exception e){ e.printStackTrace(); } //} } }, 100); //この例ではスケジュール後、100msの遅延で処理実行 } } }
この場合、timer.scheduleメソッドはタスクをスケジュール後は即時にリターンするので、フィルタチェインの実行への影響を最小限にできる。また、Timerクラスのjavadocとソースコードを読むと判るが、同メソッドは内部で直列化(同期化)されたキューにタスクを登録するため、コメントアウトされているように、応答時間の履歴を格納するマップに対してのsynchronizedは不要のはずだ。
(※実際のサーブレットフィルタの実装に必要な他のメソッド(init, destroy)や、コレクションのライフサイクル等は考慮していないので、あしからず)