質問するログイン新規登録

回答編集履歴

4

\)が足りないのを修正

2017/01/25 10:45

投稿

mit0223
mit0223

スコア3401

answer CHANGED
@@ -24,7 +24,7 @@
24
24
  @Override
25
25
  public void contextInitialized(ServletContextEvent paramServletContextEvent) {
26
26
  paramServletContextEvent.getServletContext().setAttribute("tmpMap",
27
- new ConcurrentHashMap<String, Integer>();
27
+ new ConcurrentHashMap<String, Integer>());
28
28
  }
29
29
  }
30
30
  ```

3

コーディング例で new new ってなってたのを修正

2017/01/25 10:45

投稿

mit0223
mit0223

スコア3401

answer CHANGED
@@ -24,7 +24,7 @@
24
24
  @Override
25
25
  public void contextInitialized(ServletContextEvent paramServletContextEvent) {
26
26
  paramServletContextEvent.getServletContext().setAttribute("tmpMap",
27
- new new ConcurrentHashMap<String, Integer>();
27
+ new ConcurrentHashMap<String, Integer>();
28
28
  }
29
29
  }
30
30
  ```

2

コーディング例の追加

2017/01/25 10:43

投稿

mit0223
mit0223

スコア3401

answer CHANGED
@@ -4,4 +4,85 @@
4
4
 
5
5
  ただし、掲示板が複数ある場合は、掲示板毎に管理する必要があるので、ハッシュテーブルなどを使う必要があると思います。
6
6
 
7
- また、複数のユーザがまったく同時に編集しようとすると、今、編集中のユーザがいないことを確認する if 文とユーザ情報を設定する setAttribute 呼び出しを同時に実行してしまう可能性があるため、 synchronized なメソッドで実装して排他制御する必要があると思います。
7
+ また、複数のユーザがまったく同時に編集しようとすると、今、編集中のユーザがいないことを確認する if 文とユーザ情報を設定する setAttribute 呼び出しを同時に実行してしまう可能性があるため、 synchronized なメソッドで実装して排他制御する必要があると思います。
8
+
9
+ ---
10
+ 以下に排他制御について書いてみますが、手元に servlet をデバッグする環境がないので、すべて未検証(コンパイルエラーがでるかもしれない)であることをご了承ください。
11
+
12
+ いろいろ、ググッて調べたのですが、「もし、未登録であれば登録する」というロジックで満足に排他制御できているサンプルが見つかりませんでした。ServletContext は複数のスレッド(ユーザからのリクエスト)から同時にアクセスされるので、「もし、未登録であれば」という if 文を2つのスレッドが同時に実行すると、両方のスレッドが「登録する」処理を実行してしまうので、バグってしまいます。また、この手のバグはなかなか顕在化しないので、たちが悪いです。
13
+
14
+ まず、ServletContext にマップを登録する処理ですが、初期化イベントのリスナーで実行しておくべきです。アプリケーションが動作する前に実行されるので、排他制御の必要がありません。
15
+
16
+ ```java
17
+ import javax.servlet.ServletContextEvent;
18
+ import javax.servlet.ServletContextListener;
19
+ import java.util.concurrent.ConcurrentHashMap;
20
+ @javax.servlet.annotation.WebListener
21
+ public class WebListener implements ServletContextListener {
22
+ @Override
23
+ public void contextDestroyed(ServletContextEvent paramServletContextEvent) {}
24
+ @Override
25
+ public void contextInitialized(ServletContextEvent paramServletContextEvent) {
26
+ paramServletContextEvent.getServletContext().setAttribute("tmpMap",
27
+ new new ConcurrentHashMap<String, Integer>();
28
+ }
29
+ }
30
+ ```
31
+
32
+ こうしておいて、サーブレット側では ConcurrentHashMap の putIfAbsent を使って排他制御します。
33
+
34
+ ```java
35
+ package wiki;
36
+
37
+ import java.io.IOException;
38
+ import java.util.concurrent.ConcurrentHashMap;
39
+
40
+ import javax.servlet.ServletContext;
41
+ import javax.servlet.ServletException;
42
+ import javax.servlet.http.HttpServlet;
43
+ import javax.servlet.http.HttpServletRequest;
44
+ import javax.servlet.http.HttpServletResponse;
45
+ import javax.servlet.http.HttpSession;
46
+
47
+ import Account.AccountBean;
48
+
49
+ public class UpdateUserCheck extends HttpServlet {
50
+
51
+ @Override
52
+ public synchronized void doGet(HttpServletRequest request, HttpServletResponse response)
53
+ throws ServletException, IOException {
54
+
55
+ HttpSession session = request.getSession(false);
56
+ ServletContext sc = getServletContext(); // アプリケーションスコープの取得
57
+
58
+ String name = request.getParameter("name"); // 編集中のページ名を取得
59
+ AccountBean account = (AccountBean) session.getAttribute("account");
60
+
61
+ /* 初期化イベントリスナーで登録したMapを取得する */
62
+ ConcurrentHashMap<String, Integer> map = autoCast(sc.getAttribute("tmpMap")); // メソッドでキャスト
63
+
64
+ String message = null;
65
+ final Integer currentUid = autoCast(map.putIfAbsent(name, account.getId()));
66
+ if (currentUid != null) {
67
+ if (! currentUid.equals(account.getId())) {
68
+ message = "他のユーザーにて編集中です";
69
+ request.setAttribute("message", message);
70
+ request.getRequestDispatcher("list").forward(request, response);
71
+ } else {
72
+ request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response);
73
+ }
74
+ } else {
75
+ request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response);
76
+ }
77
+ }
78
+
79
+ /* 戻り値の型に合わせてキャスト */
80
+ @SuppressWarnings("unchecked")
81
+ public static <T> T autoCast(Object obj) {
82
+ T castedObject = (T) obj;
83
+ return castedObject;
84
+ }
85
+ }
86
+ ```
87
+
88
+ また、ユーザが編集を終了するときに map から削除するようにしておかないと、上記コードは動作しません。さらに編集状態のまま、ログアウト時や放置されてセッションがタイムアウトした時も編集状態を開放する処理が必要であると思われます。

1

排他制御の必要性を追記

2017/01/25 10:32

投稿

mit0223
mit0223

スコア3401

answer CHANGED
@@ -2,4 +2,6 @@
2
2
 
3
3
  [Servlet スコープとは?](http://d.hatena.ne.jp/okoba23/20080504/1209914754)のapplicationスコープのサンプルを参照してください。 ServletContext に編集中のユーザの ID と名前を setAttribute し、あとで getAttribute することで現在編集中のユーザの情報を管理することができると思います。
4
4
 
5
- ただし、掲示板が複数ある場合は、掲示板毎に管理する必要があるので、ハッシュテーブルなどを使う必要があると思います。
5
+ ただし、掲示板が複数ある場合は、掲示板毎に管理する必要があるので、ハッシュテーブルなどを使う必要があると思います。
6
+
7
+ また、複数のユーザがまったく同時に編集しようとすると、今、編集中のユーザがいないことを確認する if 文とユーザ情報を設定する setAttribute 呼び出しを同時に実行してしまう可能性があるため、 synchronized なメソッドで実装して排他制御する必要があると思います。