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

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

ただいまの
回答率

90.12%

synchlonizedブロックについて

解決済

回答 2

投稿

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

退会済みユーザー

Javaのsynchlonizedブロックについての質問です。
http://gomyownway.hatenablog.com/entry/2012/08/30/234602
こちらを参考にしました。

public class TestThread2 {
    public static void main(String[] args){
        final Kanojyo kanojyo = new Kanojyo();
        new Thread(){
            public void run(){
                System.out.println("スレッド-"+Thread.currentThread().getName()+"が走っています");

                //彼女pブジェクとのロックを取得する
                synchronized(kanojyo){
                    //sleepしてみる
                    try{
                        //ロックを取得したので優雅に2秒間待って・・・
                        System.out.println(Thread.currentThread().getName()+"は待機中です・・・");
                        sleep(2000);
                    }catch(InterruptedException e){}

                    //彼女の名前をセットする
                    kanojyo.setKanojyoName("Shiho");
                    System.out.println("彼女の名前をセットしました");
                }
            }
        }.start();

        new Thread(){
            public void run(){
                try{
                    //上で彼女をロックする前にこっちがロックを取得するのは困るので、一瞬待機
                    sleep(20);
                } catch(InterruptedException e){}
                System.out.println("スレッド-"+ Thread.currentThread().getName()+"が走っています");
                //いくらスレッドが走っても、ロックを取られているので、setが終わるまでgetKanojyoName()は実行できない
                synchronized(kanojyo){
                    System.out.println("彼女の名前は"+kanojyo.getKanojyoName());
                }
            }
        }.start();
    }
}

class Kanojyo{
    private String name = null;

    public void setKanojyoName(String name){
        this.name = name;
    }

    public String getKanojyoName(){
        return name;
    }
}


実行結果は、
スレッド-Thread-0が走っています
Thread-0は待機中です・・・
スレッド-Thread-1が走っています
彼女の名前をセットしました
彼女の名前はShiho

のようになります。
上のコードで対象インスタンス(synchlonizedブロックのカッコで囲まれた部分のことです)をkanojyoからthisに変更すると、実行結果は変わってしまいます。
よって、対象インスタンスのロックを獲得しているスレッドのみが対象インスタンスのメンバにアクセスできるのだと考えています。
しかしながら、以下のようなコードではクラス内でインスタンスを作って、それを対象インスタンスとしています。
これって意味あるのでしょうか?

class Display {
    private static HashMap _classnameToInstance = new HashMap();
    private static Object _lock = new Object();
    protected Display() {
        synchronized (_lock) {
            String classname = this.getClass().getName();
            if (_classnameToInstance.get(classname) != null) {
                throw new RuntimeException("Already created: " + classname);
            }
            _classnameToInstance.put(classname, this);
        }
    }
    public static Display getInstance(String classname) {
        synchronized (_lock) {
            Display obj = (Display)_classnameToInstance.get(classname);
            if (obj == null) {
                try {
                    Class cls = Class.forName(classname);
                    obj = (Display)cls.newInstance();
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(classname + " is not found");
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(classname + " cannot be accessed.");
                } catch (InstantiationException e) {
                    throw new RuntimeException(classname + " cannot be instantiated.");
                }
            }
            return obj;
        }
    }
    public void display(String msg) {
        System.out.println("Display: " + msg);
    }
}


こちらのコードは
http://www.hyuki.com/techinfo/singleton.html
を参考にしています。

回答お願いします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

checkベストアンサー

+2

よって、対象インスタンスのロックを獲得しているスレッドのみが対象インスタンスのメンバにアクセスできるのだと考えています。

違います。 synchronized 自身には対象オブジェクトのメンバ変数に対するアクセス制御の意味はありません。
しかし、オブジェクトへのアクセスを排他制御したい場合は、別のオブジェクトを作るのも無駄なので、一般的にそのオブジェクト自身を synchronized の対象にします。

しかしながら、以下のようなコードではクラス内でインスタンスを作って、それを対象インスタンスとしています。 
これって意味あるのでしょうか?

あります。このオブジェクト_lockはクラスの static 変数なので、いわゆるシングルトンです。排他制御をオブジェクト単位ではなく、アプリケーション全体で1個としたい場合にはシングルトンを対象として synchroized します。
ただし、例に挙げられているものについては、_classnameToInstanceという別のシングルトンを持っているので、これを synchronized の対象にしても実装できてると思いますので、シングルトンの排他制御の例としてはいまいちだと思います。
たとえば、何か重い初期化ををオンデマンドに実行するとか、Java 以外のリソースへのアクセスを排他制御したい場合などはシングルトンのロック専用オブジェクトを作ることになると思います。

ロックの概念
ロックにはアドバイザリロックと強制ロックがあります。Java の synchronized はアドバイザリロックです。つまり、排他制御に参加するスレッドが互いに synchronized をする約束のもとに成り立っています。誰かが約束を破って synchronized をかけずにアクセスすると、オブジェクトのメンバを更新できたりして、排他制御が崩れてバグになります。
一方で、ファイルのロックなどにおいては、強制ロックが実装されている場合があります。これは、OSレベルでロックが掛かり、一つのプロセスがロックをかけると、他のプロセスからは排他制御に参加する気がなくてもアクセスできなくなります。
一般的に、強制ロックはアドバイザリロックに比べて、コストが高いです。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/09/12 10:38

    この例のようにmainスレッドが終了した場合もなお無名クラスのメンバ変数として残るという点では無名クラス及びラムダ式は特殊だと思いました。
    そういて点でローカルクラスとは異なるのでしょうか?
    つまり、ローカルクラスでは外部(アウタークラスや囲んでいるメソッド)の変数にアクセスできるが、クロージャーのように実行結果を暗記(技術的にはこの表現は正確ではないかもしれません)はできない、という意味で異なるのでしょうか?

    キャンセル

  • 2016/09/12 11:18

    はい、無名クラス及びラムダ式は暗黙のメンバ変数が作られる(ローカル変数が生き残る?)ところが特殊です。したがって、ローカルクラスとは意味が違います。この特殊性を用いて、テンポラリな(でもメソッドの実行期間を超えて生き残れる)変数を作れるところが、この技法のメリットなわけです。

    キャンセル

  • 2016/09/12 20:06

    長々とおつきあいいただき、ありがとうございました。

    キャンセル

+1

あるオブジェクトのロックを取得することと、そのオブジェクト内部へのアクセス権を取得することは全くの無関係です。
例えるなら次のような状況です。
A,Bの二人で同時に作業していますが、それぞれある作業ではある工具を必要とします。しかし、その工具は1本しかありません。この工具を自分の手に持つということがその工具の「ロックを取得する」という事になります。例えばAが工具を手にしたら、Bはその工具を使えません。Aが作業を終了したら、工具を元の場所に戻します。これが「ロックを開放する」ことに相当します。工具が元の場所に戻れば、今度はBがその工具を手にし(ロックを取得し)、作業をすることができます。これが最初のコードです。
この仕組みにより、Aが先に作業を完了(kanojoの名前を変更する)し、その後Bが作業する(kanojoの名前を表示する)ことで、順番を確実にしているのです。
「工具を使う」という表現をしましたが、実際にはその工具を必ずしも使う必要はありません。ただ「その工具を持たないと作業ができない」というのがsynchronizedブロックの目的です。

そのあとの、

上のコードで対象インスタンス(synchlonizedブロックのカッコで囲まれた部分のことです)をkanojyoからthisに変更すると、実行結果は変わってしまいます。 

は、AとBで必要とする工具が違うので、Aが工具を持っていてもBは目的の工具を問題なく手に取る事ができます。そのため、Aが作業を完了する前にBの作業が入る可能性があるため、実行結果が異なるのです(この場合、Aが実質的な作業をするまで2秒開けているので、Bの作業が先に来るのは確実)。

しかしながら、以下のようなコードではクラス内でインスタンスを作って、それを対象インスタンスとしています。 
これって意味あるのでしょうか?

上記「工具」に相当するものをクラスで唯一のものとしておくことで、この工具を使った作業が2つ以上同時進行しないようにするためのものです。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/09/08 22:57

    上記のコードでは2つのsynchlonizedブロックがあると思うのですが、その二つとも対象インスタンスはthisを指定しました。
    その場合でも、AとBで必要とする工具が違うので、Aが工具を持っていてもBは目的の工具を問題なく手に取る事ができるという状況が生じるのでしょうか?
    結局対象インスタンスの違いでロックの仕方に違いが生じるのですよね?

    キャンセル

  • 2016/09/09 00:23

    その状況でthisを使った場合、そのthisはAおよびB自身を指すことになります。つまり実行する人自身です。
    AはA自身の仕事をするため自身を取得し、他の人に呼ばれてもその人のためには行動できない状態にするのがsynchronized(this)の意味になります。BについてもB自身をロックする事になります。ロック取得の対象がそもそも違うので、同時に実行できるのです。

    キャンセル

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

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

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