synchlonizedブロックについて
解決済
回答 2
投稿
- 評価
- クリップ 1
- VIEW 2,846

退会済みユーザー
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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+2
よって、対象インスタンスのロックを獲得しているスレッドのみが対象インスタンスのメンバにアクセスできるのだと考えています。
違います。 synchronized 自身には対象オブジェクトのメンバ変数に対するアクセス制御の意味はありません。
しかし、オブジェクトへのアクセスを排他制御したい場合は、別のオブジェクトを作るのも無駄なので、一般的にそのオブジェクト自身を synchronized の対象にします。
しかしながら、以下のようなコードではクラス内でインスタンスを作って、それを対象インスタンスとしています。
これって意味あるのでしょうか?
あります。このオブジェクト_lock
はクラスの static 変数なので、いわゆるシングルトンです。排他制御をオブジェクト単位ではなく、アプリケーション全体で1個としたい場合にはシングルトンを対象として synchroized します。
ただし、例に挙げられているものについては、_classnameToInstance
という別のシングルトンを持っているので、これを synchronized の対象にしても実装できてると思いますので、シングルトンの排他制御の例としてはいまいちだと思います。
たとえば、何か重い初期化ををオンデマンドに実行するとか、Java 以外のリソースへのアクセスを排他制御したい場合などはシングルトンのロック専用オブジェクトを作ることになると思います。
ロックの概念
ロックにはアドバイザリロックと強制ロックがあります。Java の synchronized はアドバイザリロックです。つまり、排他制御に参加するスレッドが互いに synchronized をする約束のもとに成り立っています。誰かが約束を破って synchronized をかけずにアクセスすると、オブジェクトのメンバを更新できたりして、排他制御が崩れてバグになります。
一方で、ファイルのロックなどにおいては、強制ロックが実装されている場合があります。これは、OSレベルでロックが掛かり、一つのプロセスがロックをかけると、他のプロセスからは排他制御に参加する気がなくてもアクセスできなくなります。
一般的に、強制ロックはアドバイザリロックに比べて、コストが高いです。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+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つ以上同時進行しないようにするためのものです。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.36%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2016/09/07 23:26
>違います。 synchronized 自身には対象オブジェクトのメンバ変数に対するアクセス制御の意味はありません。
正直わかっていないと思うのですが、質問させてください。
しかし、インスタンスの同期を取るためにsynchlonizedブロックは使われると思うのですが、その際対象インスタンスに指定されたインスタンスの同期が取れるようになりますね?
それってメンバへのアクセスを意味するのではないでしょうか?
私があげたコードだとkanojyoクラスのメソッドやフィールドにはロックを取得したスレッドしかアクセスできないと思ったのですが。
後半の回答を見ますと、対象インスタンスは何でもいいと思ってしまったのですが、そういうことなのでしょうか?
2016/09/07 23:37
Java の synchronized はアドバイザリロックです。解説を回答に追記しておきます。
2016/09/07 23:58
2016/09/08 00:22
2016/09/08 22:47
>アドバイザリロックなので、排他制御に参加するスレッド間でどのように約束するかという問題です。例のプログラムでは、kanojyoにアクセスするときは synchronized(kanojyo) するというのが約束であるということです。でも、kanojyoにアクセスするときは synchronized(kareshi) するという約束でも動作します。
対象インスタンスとしてthisを指定して実行すると、同期ができなくなったわけですが、これはどのようにして説明するのでしょうか?
オブジェクトはkanojyoでなくてはならないのですよね?
2016/09/08 23:10
new Thread(){
public void run(){
この書き方はいわゆる無名クラスです。この run のメソッド定義の中で this を参照すると、 Thread クラスの無名のサブクラスのインスタンス(ああ、ややこしい)を参照することになります。したがって、 Thread-0 と Thread-1 では自分のスレッドのインスタンスを参照することになり、別オブジェクトになります。別オブジェクトで synchronized しても排他制御になりません。アドバイザリロックで排他制御する場合は、スレッド間で共通の1個のオブジェクトに対して syncronized する必要があります。こういう間違いを減らすためにも Kanojo クラスのメソッドの中で synchronized(this) というように記述すべきです。
2016/09/08 23:36
無名クラスの中でメソッド定義(この場合 run メソッド)する場合、その自由変数(メソッド内でも引数でも宣言されてない変数で、この場合 kanojo )はその定義が出現するメソッド(ここでは main)内のローカル変数を参照できます。
つまり、Thread-0, Thread-1 を実行する run() 内の変数 kanojo は new Thread() が実行された時点のローカル変数 kanojo の値を参照します。
これは Java では無名クラスだけでできるわざです。1.8から何か新しいのが導入されて他の方法もありますが、今思い出せません。 JavaScript とかではクロージャという概念があって、このことが日常茶飯事で、それが JavaScript の優れた点であると言われてます。
2016/09/08 23:40
対象インスタンスとしてシングルトンを指定するのとそうでないもの(例えばthis)とするのではどういう違いがあるのでしょうか?
スレッドの側からすると、(無名クラスではなく、同一のインスタンスを参照する)thisが指定されようと、シングルトンが指定されようと排他制御が行われるわけで、違いはないように思えたのですが、そうではないのですよね?
2016/09/08 23:53
他のクラス(通常のクラスやローカルクラス、メンバクラス)ではそのクラス内で自由変数を使うとエラーになる、ということでまちがいないでしょうか??
2016/09/08 23:58
こういうのをロックの粒度といいます。たとえば、リレーショナルデータベースでは、データベースまるごとロックしたり、特定のテーブルをロックしたり、テーブルの中の特定のレコードをロックしたり、レコードの中の特定のフィールドをロックできたりします。さらにこれらを混ぜて利用する場合は複粒度ロッキングと言って技術的に真似するのはとてもむずかしくなります。
2016/09/09 00:09
クラスの static 変数→最初から最後まで存在
クラスのメンバ変数→インスタンスがある間のみ存在
メソッドのローカル変数→メソッドの実行中のみ存在
無名クラスのメソッド定義内の自由変数→インスタンスがある間のみ存在
で、自由変数の最大の特長は宣言がないのに自動的にクラスのメンバ変数になっていることです。このことをわざと言いました。 kanojo については、本来メソッドのローカル変数なのにメソッドが終了しても参照できます。それは、実はメソッドのローカル変数を参照しているのではなく、 new Thread を実行した時点で暗黙のメンバ変数が作られているという意味付けになってるからなんです。
2016/09/09 00:21
> 他のクラス(通常のクラスやローカルクラス、メンバクラス)ではそのクラス内で自由変数を使うとエラーになる、ということでまちがいないでしょうか??
はい、自由変数は宣言されていない変数という意味で、これでまちがいありません。
2016/09/09 22:48
おかげで対象インスタンスや排他制御については理解できました。
さて、後半の無名クラスの特徴についてですが、
>kanojo については、本来メソッドのローカル変数なのにメソッドが終了しても参照できます。それは、実はメソッドのローカル変数を参照しているのではなく、 new Thread を実行した時点で暗黙のメンバ変数が作られているという意味付けになってるからなんです。
後半のこの文章がよく理解できませんでした。
kanojyoは本来メソッドのローカル変数であると言われていますが、ここでいうメソッドはどのメソッドでしょうか?
無名クラスの中のメソッドでしょうか?
それとも無名クラスの定義がなされるメソッドでしょうか?
2016/09/11 16:40
ついでに「1.8から何か新しいのが導入されて他の方法もありますが、今思い出せません」は「ラムダ式」でした。あと、Java のこの技法では対象の変数が final でなければならず、厳密にはクロージャではありません。JavaScript で同様に外側のローカル変数を自由変数で参照すると値を変更できたりします。
2016/09/11 23:19
自由変数を書きかえることのできる手段として、無名クラスやラムダ式があることは理解できたのですが、ローカルクラス(メソッド内で宣言するクラス)はこれらの仲間とは言えないのでしょうか?
と言いますのも、ローカルクラスはその定義がなされているメソッド内のfinalが宣言された変数及び定義されているクラスのすべての変数にアクセスできます。
2016/09/12 10:38
そういて点でローカルクラスとは異なるのでしょうか?
つまり、ローカルクラスでは外部(アウタークラスや囲んでいるメソッド)の変数にアクセスできるが、クロージャーのように実行結果を暗記(技術的にはこの表現は正確ではないかもしれません)はできない、という意味で異なるのでしょうか?
2016/09/12 11:18
2016/09/12 20:06