1

マルチスレッドを利用するプログラムを書くのはこれが初めてなので、プログラムでの並行性の使用に関していくつか質問があります。

私のプログラムは、Web UI からユーザー入力を受け取り、そのユーザー入力でプロセスを開始します。このプロセスには 1 時間以上かかるため、並行性を利用する必要があることはわかっています。また、あるプロセスが完了するのを待ってから次のプロセスを開始することはできません。

次の簡略化されたコードは、ユーザー入力を処理してからプロセスを開始します。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String myInput = request.getParameter("input");
    Thread t = new Thread(new MyRunnable(myInput));
    t.start();

    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("Process started!");
    out.close();
}

次のコードは、実際のプロセスを簡略化したものです。

public class MyRunnable implements Runnable {

    private static HashMap<String,String> mapOfConstants = null;

    private String member;

    public MyRunnable(String member) {
        this.member = member;
    }

    @Override
    public void run() {
        if (mapOfConstants == null) init();
        // and so on...
    }

    private void init() {
        mapOfConstants = new HashMap<String,String>();
        mapOfConstants.put("LOCATION", "http://localhost/folder");
        // and so on...
    }

}

上記のコードでは、一連のプレースホルダーを HashMap に格納される定数として定義するつもりですmapOfConstants

編集:最終的には、このマップの初期化が他の場所、たとえばテキスト ファイルから値を取得するようにしたい場合があります。

私のコードは、このプレースホルダ マップを のすべてのインスタンスで共有しMyRunnable、この初期化プロセスを 1 回だけ実行するという目的を達成していますか?

4

5 に答える 5

1

私はこれが強い反応を受けないことを知っていますが、あなたのコードはほとんど問題ありません. 厳密にはスレッド セーフではありませんが (プロパティを 1 回だけ読み込むという点で)、それでも正しい場合があります (破損したデータを作成しないという点で)。

大きな変更点は次のとおりです。

private void init() {
    HashMap<String,String> tempMap = new HashMap<String,String>(); // <--- new object assigned to a placeholder variable
    tempMap.put("LOCATION", "http://localhost/folder");
    // and so on...
    mapOfConstants = Collections.unmodifiableMap(tempMap); // <--- atomic assignment here
}

これが実際にファイルからロードされ、決して変更されない標準的なプロパティのセットであると仮定するとmapOfConstants、最初のいくつかのタスクがそれぞれマップが null であると考えてロードするという大きな「リスク」があります。コードが書かれているので、複数のスレッドが同時にコードを変更するというさらなるリスクがあります。上記の変更されたコードを使用すると、複数のバージョンのマップが存在する可能性がありますが、マップはアトミックに割り当てられるため、すべてのバージョンが正しく、破損することはありません。最終的に、JVM はどのマップがその静的メンバーに現在関連付けられているマップであるかを分類し、その他のコピーはゴミとして収集されます。

于 2013-07-02T21:03:59.790 に答える
1

私はあなたの質問 1 に答えています。2 については別の質問を投稿してください。

私のコードは、このプレースホルダー マップを MyRunnable のすべてのインスタンスで共有し、この初期化プロセスを 1 回だけ実行するという目的を達成していますか?

はい、ただしスレッドセーフではありません。したがって、次の 2 つのオプションがあります。

私の答えは、定数のマップであると言ったので、実行時にマップの内容を変更したくないことを前提としています。

  • オプション 1: マップfinalを作成し、それをブロックで使用Collections.unmodifiableMapして初期化しstaticます。これにより、コードもスレッド セーフになります。

  • オプション 2: (同期) ここでは明らかに不要な遅延初期化を使用する場合は、コードをスレッド セーフにする必要があります。あなたのコードはスレッドセーフではありません。

理由: 実行中の複数のスレッドがマップを null として認識し、init を呼び出す可能性があります。これにより、マップが複数回初期化されます。synchronizedブロックを使用します。

//keeping map `volatile`
private static volatile HashMap<String,String> mapOfConstants = null;

...
if(map == null)
  synchronized(SomeClass.class){
     if(map == null){
        init();
     }
  }
于 2013-07-02T09:30:40.803 に答える
0

はい、静的メンバーを一度初期化してスレッド間で共有できますが、十分な注意を払った場合に限ります。たとえば、すべてのinit()呼び出しサイトを同期する必要があります。つまり、init()は、コンストラクターまたは同期メソッドからのみ呼び出すことができます。または、静的初期化ブロックでmapOfConstants変数を初期化することもできます。どちらのアプローチを使用する場合でも、java.util.concurrent.ConcurrentHashMapの実装をmapOfConstants変数の具象型として検討することをお勧めします。これにより、後で頭痛の種が回避されるからです。

十分な注意を怠ると、「ダブルチェック ロック」競合状態に遭遇する可能性があります。また、静的参照変数の初期化は通常、アンチパターンと見なされます。この場合、マップ内のエントリは、プログラムの 1 回の起動内で際限なく大きくなる可能性があるためです。通常、そのような参照の唯一の許容可能な使用法は、コンテンツが一定である場合、またはコンテンツが非常にゆっくりと (対数的に考えて) 時間とともに成長する場合です。対数的増加は、java.util.WeakHashMapを使用するか、それができない場合は弱参照を賢明に使用することによって支援される場合があります。

これまでのところ、静的なmapOfConstants変数の初期化に重点が置かれているようです。しかし、これは、マップの全体的な目的が (通常) 最良の場合に O(1) 時間で後で取得できるように何かを格納することであることを忘れています。( mapOfConstants変数に対する)格納操作と取得操作がスレッド境界を越える場合、それらの操作も同期する必要があることに注意してください。あるスレッドによる同期、追加、または編集の欠落は、他のスレッドによって見落とされる可能性があり、プログラムのデータの整合性に影響を与える可能性があります。

于 2013-07-02T20:49:01.200 に答える
0

まず第一に、run()メソッドでマップを初期化しないでください。初期化が 1 回だけ行われるという保証はありません。確かにこの場合、そのマップを何回作成したかは関係ありません。最終的には、1 つが静的参照に設定され、他は GC されます。それはちょうどきれいではありません。静的初期化ブロックをお勧めします:

private final static Map<String,String> mapOfConstants;
static {
    Map<String, String> map = new HashMap<String, String>();
    // initialize map.
    map.put("", "");
    ...

    // convert the map into unmodifiable
    mapOfConstants = Collections.unmodifiableMap(map);
}

ただし、複数のスレッド間で定数のマップを共有する別の方法があります。テキスト ファイルから定数をロードすることを検討しているため、Runnable から静的参照を抽出し、別の場所でマップを初期化してから、参照を渡すことを検討しましたか? コンストラクターは、追加のマップ参照を受け取ります。

public class MyRunnable implements Runnable {

    private final Map<String,String> mapOfConstants = null;

    private String member;

    public MyRunnable(String member, Map<String,String> mapOfConstants) {
        this.member = member;
        this.mapOfConstants = mapOfConstants;
    }
    ....
}

次に、サーブレットで、マップ参照を取得しますMapOfConstantsFactory

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws     ServletException, IOException {
    Map<String, String> sharedMapOfConstants = MapOfConstantsFactory.getMapOfConstants();
    String myInput = request.getParameter("input");
    Thread t = new Thread(new MyRunnable(myInput, sharedMapOfConstants));
    t.start();

    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("Process started!");
    out.close();
}

このようにして、Runnable クラスを変更することなく、定数のさまざまな構成に対してテストを作成できます。

于 2013-07-02T09:31:17.070 に答える