質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.91%

マルチスレッドによる計算

解決済

回答 5

投稿

  • 評価
  • クリップ 0
  • VIEW 2,360

snowman

score 25

以下のようにマルチスレッドを使ってある数に1を永遠とたすプログラムを組んだのですが、スレッドが切り替わるたびに計算結果がずれてしまいます・・・
原因と解決方法を教えてください・・・

public class MessyCounter extends Thread {

    static int counter;
    private int id;
    MessyCounter(int id) {
             this.id = id;
    }
       public synchronized void run() {
           while (true) {
                 System.out.println(id + ":" +counter);
                counter++;
           }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread t[]=new Thread[4];
        for(int i=0;i<4;i++){
            t[i]=new Thread(new MessyCounter(i));
        }
        t[0].start();
        t[1].start();
        t[2].start();
        t[3].start();

    }

}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • yohhoy

    2017/05/18 22:07

    そもそも、どういう結果を望んでいる(望ましい実行結果)のでしょうか?

    キャンセル

  • snowman

    2017/05/18 23:03

    コンソールに表示されるcounterの値がしっかりと数字順に並ぶのが望ましい実行結果です。

    キャンセル

回答 5

checkベストアンサー

+3

 Before
public synchronized void run() {
    while (true) {
        System.out.println(id + ":" + counter);
        counter++;
    }
}
 After
public void run() {
    while (true) {
        displayAndIncrement(id);
    }
}

private static synchronized void displayAndIncrement(int id) {
    System.out.println(id + ":" + counter);
    counter++;
}

修正前のコードでは、MessyCounterクラスのインスタンスごとにrun()メソッドを同期化していることになります。
https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.3.6

For an instance method, the monitor associated with this (the object for which the method was invoked) is used.

そのため、ご提示のコードのように複数のスレッドが別々のインスタンスのrun()メソッドからcounterフィールドにアクセスした場合、
同期化されていないのと同じ結果になります。

一方、私の回答のように 同期化された staticメソッドからcounterフィールドにアクセスするようにすると、
MessyCounterクラスの Classオブジェクトによって同期化されるため、意図通りの動作になります。
上と同じリンク

For a class (static) method, the monitor associated with the Class object for the method's class is used.

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+2

スレッドが切り替わるたびに計算結果がずれてしまいます・・・
コンソールに表示されるcounterの値がしっかりと数字順に並ぶのが望ましい実行結果です。

(1)マルチスレッド処理に関する大きな誤解と、(2)Java言語による実装上の問題 があります。


(1) 一言で言ってしまえば、マルチスレッド・プログラムはあなたの考える通りには動きません。

「各スレッドの処理が同じ早さで均等に進む」「ラウンドロビン方式で1ステップづつ処理される」ことを期待されているようですが、マルチスレッド処理の一般論として、そのような実行結果になることはありません。

今回の掲示プログラムで言えば、4つのスレッドがそれぞれ変数counterをインクリメント/値を参照する タイミングを予測することは不可能 です。そのため、ありとあらゆる実行結果が起こりえます。


(2) Java言語では、通常のメンバ変数を複数スレッドから 同時にアクセス(読み/更新) すると、予期しない結果をもたらすことがあります。他回答にある「排他制御/同期化が必要」という指摘は、このような同時アクセスを禁止するための手段です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

public class MessyCounter extends Thread {
    private static int counter;
    private static final Object locker = new Object();//ロック用オブジェクト
    private final int id;// finalを追加

    MessyCounter(int id) {
        this.id = id;
    }

    public void run() {
        while (true) {
            synchronized (locker) {
                System.out.println(id + ":" + counter);
                counter++;
            }
        }
    }

    public static void main(String[] args) {
        Thread t[] = new Thread[4];
        for (int i = 0; i < t.length; i++) {
                        // MessyCounterはThreadを継承しているため、new Threadを削除。
            t[i] = new MessyCounter(i);
        }
        for (Thread thr : t) {
            thr.start();
        }
    }

}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

スレッドは、一つ一つ処理速度が違います。
なので、counterをアクセス時に他スレッドで足された後にアクセスするケースがズレにあたると思われます。

逆に質問しますが、同時に[0][1][2][3]が同じ値のcounterを取得するということでいいんでしょうか?
それとも[0] = 0, [1] = 1, [2] = 2, [3] = 3, [0] = 4, [1] = 5.....といったかんじでしょうか?

無理やりずらしてみました。

public class StartApp extends Thread {

    static int counter;
    private int id;

    StartApp(int id) {
        this.id = id;
    }

    public synchronized void run() {
        while (true) {
            System.out.println(id + ":" + counter);
            counter++;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO 自動生成された catch ブロック
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread t[] = new Thread[4];
        for (int i = 0; i < 4; i++) {
            t[i] = new Thread(new StartApp(i));
        }
        try{
        t[0].start();
        Thread.sleep(100);
        t[1].start();
        Thread.sleep(100);
        t[2].start();
        Thread.sleep(100);
        t[3].start();
        Thread.sleep(100);
        } catch (InterruptedException e){

        }
    }

各スレッドの生成を遅らせて生成を試みてみました。
正直無理やり感がぬぐえないのとテスト回数が少ないので正しいかどうかはわかりませんが一応貼っておきます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/18 22:56

    [0] = 0, [1] = 1, [2] = 2, [3] = 3, [0] = 4, [1] = 5.....といった感じです!

    キャンセル

0

runメソッドを「synchronized」にしているのに、排他的にcounterが加算されないことが疑問点でしょうか?どのオブジェクトについてロックを取得しようとしているかを考察いただけますと、解決の手掛かりになるかと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/18 23:00

    すみません、こういう指示の課題を出され、スレッドについての説明もまともにないまま「あとは自分で調べるなりなんなりして頑張れ」といった感じで丸投げされてしまったためロックの仕様についてしっかりと把握していません・・・。
    なのでrunについた「synchronized」については「こんな感じかな・・・?」と思い適当につけて見た次第です・・・。

    キャンセル

  • 2017/05/19 00:04 編集

    なんと、放任主義的な先生のようですね。
    「排他制御」「マルチスレッド」などのキーワードで検索しますと、今回の課題の参考になりそうな記事が色々見つかるかと思います。言語がJavaではないものも多いかと思いますが、こういった競合回避のための方法に大きな違いはないはずです。いろいろなサイトをご覧になれば、概念の理解が進むと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る