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

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

ただいまの
回答率

87.94%

Javaのスレッドの対象インスタンスについて

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 2,145
退会済みユーザー

退会済みユーザー

Javaのsynchronized修飾子の対象インスタンスについての質問です。
http://www.tohoho-web.com/java/thread.htm
こちらの記事を参考にしました。

// テストプログラム
class SyncTest {
    static Counter counter = new Counter();

    public static void main(String[] args) {

        // スレッドを1000個作成する
        MyThread[] threads = new MyThread[1000];
        for (int i = 0; i < 1000; i++) {
            threads[i] = new MyThread();
            threads[i].start();
        }

        // スレッドがすべて終了するのを待つ
        for (int i = 0; i < 1000; i++) {
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                System.out.println(e);
            }
        }

        // カウンターを表示する
        System.out.println(SyncTest.counter.count);
    }
}

// スレッド
class MyThread extends Thread {
    public void run() {
        SyncTest.counter.countUp();
    }
}

// カウンター
class Counter {
    int count ;
    void countUp() {
        System.out.print("[");
        int n = count;            // カウンターを読み出して
        System.out.print(".");
        count = n + 1;            // 加算して書き戻す
        System.out.print("]");
    }
}


「この問題(スレッドの競合のことです)を防ぐには、同期処理 や 排他制御 と呼ばれる制御を行います。下記のように synchronized を用いることで、(...) で指定したオブジェクト(下記の例では this、つまり Counter オブジェクト)に対してロック権を取得した単一のスレッドのみが { ... } の処理を実行できるようになります。
(中略)
synchronized メソッドは、常にひとつのスレッドのみがそのメソッドを実行すると誤解されがちですが、スレッドを実行するインスタンスが複数あれば、インスタンスの個数だけ多重に実行される可能性がありますので注意してください。例えば、上記の例で add() ではなく run() メソッドを synchronized にしても、run() メソッドのインスタンスはスレッドの個数分 1000個ありますので、排他制御はうまく機能しません。」

つまり、例えばsynchronized(this)とすると、thisに対して、実行する権利を得たスレッドのみが{}で囲まれた部分を実行するわけですよね?
このコードで言えば、countUpメソッドにsynchronized修飾子をつけると、Counterインスタンスに対して、実行する権利を得たスレッドのみがブロックで囲まれた部分を実行することになるから、競合は起こらないという意味だと思います。

一方、私の使っている参考書(スッキリわかるJava入門編 第二版)に次のような記述がありました。

System.out.println("こんにちわ");
synchronized(this) {
   a +=2;
}
System.out.println("さようなら");
synchronized(this) {
   a+=3;
}
System.out.println("おやすみ");
synchronized(System.out) {
   b+=3;
}


スレッドAが最後のブロック(対象インスタンスがSystem.outのブロックです)を実行している時に、真ん中のブロックを実行中のスレッドBは真ん中のブロックを実行後、この最後のブロックに入れるというような記述があります。
この本の引用ですと、「スレッドAとは対象インスタンスが違うため、侵入可。」と書いています。
System.outインスタンスが一つではないから、スレッドAもスレッドBも許可を得られるので、実行できるということなのでしょうか。
回答お願いします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • swordone

    2016/08/20 00:08

    実行の形態がよくわからないので、この最後のコードの周辺含めて全体を書いていただけませんか?

    キャンセル

回答 1

checkベストアンサー

0

どういう状況かわからなかったので、以下の様なコードで実験してみました(本の意図と違うようならご指摘下さい)。

public class Q44822 {

    public static void main(String[] args) {
        new Test("A").start();
        new Test("B").start();
    }

    static class Test extends Thread {
        String id;
        int a = 0, b = 0;

        public Test(String id) {
            this.id = id;
        }

        @Override
        public void run() {
            System.out.println(id + ":こんにちわ");
            synchronized (this) {
                a += 2;
            }
            System.out.println(id + ":さようなら");
            synchronized (this) {
                a += 3;
            }
            System.out.println(id + ":おやすみ");
            synchronized (System.out) {
                System.out.println(id + "がout-synchronizedに進入");
                b += 3;

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO 自動生成された catch ブロック
                    e.printStackTrace();
                }

                System.out.println(id + "がout-synchronizedから脱出");
            }
        }
    }

}

結果、何度やっても、片方が最後のブロックを実行中(Thread.sleep(1000)で止まっている間)、もう片方が最後のブロックに入ってくるということはありませんでした。
根本的に考えて、System.outはstaticフィールドなのでインスタンスはただ一つです。片方がこれのロックを取得しているなら、もう片方がこのロックを取得できないので、synchronizedブロックに入れません。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/08/20 22:52

    APIリファレンスを読みました。
    以下、お答え頂ければ嬉しいです。
    特にインスタンス化もしていないのに、
    System.out.println();
    のように呼び出せますので、インスタンス化のタイミングはクラスがロードされた時でしょうか。
    また、staticフィールドだったら、インスタンスはただ一つなのはなぜでしょうか?
    調べてみましたが、わかりませんでした。

    キャンセル

  • 2016/08/20 23:36

    インスタンス化のタイミングはすみませんがわかりません。

    > staticフィールドだったら、インスタンスはただ一つなのはなぜでしょうか?
    クラス内で唯一の存在だからこそ、staticなのです。(Systemクラスはインスタンス化できませんが)非staticフィールドの場合、そのクラスのインスタンスにそのフィールドが結びついているため、インスタンスの数だけそのフィールドが存在することになります。staticフィールドはクラスに結びついており、インスタンスに依存しないクラスで唯一のものになります。

    キャンセル

  • 2016/08/21 22:39

    回答ありがとうございました。

    キャンセル

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

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

関連した質問

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