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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

2回答

4416閲覧

synchlonizedブロックについて

退会済みユーザー

退会済みユーザー

総合スコア0

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

2グッド

1クリップ

投稿2016/09/07 09:11

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

Java

1public class TestThread2 { 2 public static void main(String[] args){ 3 final Kanojyo kanojyo = new Kanojyo(); 4 new Thread(){ 5 public void run(){ 6 System.out.println("スレッド-"+Thread.currentThread().getName()+"が走っています"); 7 8 //彼女pブジェクとのロックを取得する 9 synchronized(kanojyo){ 10 //sleepしてみる 11 try{ 12 //ロックを取得したので優雅に2秒間待って・・・ 13 System.out.println(Thread.currentThread().getName()+"は待機中です・・・"); 14 sleep(2000); 15 }catch(InterruptedException e){} 16 17 //彼女の名前をセットする 18 kanojyo.setKanojyoName("Shiho"); 19 System.out.println("彼女の名前をセットしました"); 20 } 21 } 22 }.start(); 23 24 new Thread(){ 25 public void run(){ 26 try{ 27 //上で彼女をロックする前にこっちがロックを取得するのは困るので、一瞬待機 28 sleep(20); 29 } catch(InterruptedException e){} 30 System.out.println("スレッド-"+ Thread.currentThread().getName()+"が走っています"); 31 //いくらスレッドが走っても、ロックを取られているので、setが終わるまでgetKanojyoName()は実行できない 32 synchronized(kanojyo){ 33 System.out.println("彼女の名前は"+kanojyo.getKanojyoName()); 34 } 35 } 36 }.start(); 37 } 38} 39 40class Kanojyo{ 41 private String name = null; 42 43 public void setKanojyoName(String name){ 44 this.name = name; 45 } 46 47 public String getKanojyoName(){ 48 return name; 49 } 50}

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

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

Java

1class Display { 2 private static HashMap _classnameToInstance = new HashMap(); 3 private static Object _lock = new Object(); 4 protected Display() { 5 synchronized (_lock) { 6 String classname = this.getClass().getName(); 7 if (_classnameToInstance.get(classname) != null) { 8 throw new RuntimeException("Already created: " + classname); 9 } 10 _classnameToInstance.put(classname, this); 11 } 12 } 13 public static Display getInstance(String classname) { 14 synchronized (_lock) { 15 Display obj = (Display)_classnameToInstance.get(classname); 16 if (obj == null) { 17 try { 18 Class cls = Class.forName(classname); 19 obj = (Display)cls.newInstance(); 20 } catch (ClassNotFoundException e) { 21 throw new RuntimeException(classname + " is not found"); 22 } catch (IllegalAccessException e) { 23 throw new RuntimeException(classname + " cannot be accessed."); 24 } catch (InstantiationException e) { 25 throw new RuntimeException(classname + " cannot be instantiated."); 26 } 27 } 28 return obj; 29 } 30 } 31 public void display(String msg) { 32 System.out.println("Display: " + msg); 33 } 34}

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

回答お願いします。

yodel, mit0223👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

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

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

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

これって意味あるのでしょうか?

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

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

投稿2016/09/07 12:23

編集2016/09/07 14:52
mit0223

総合スコア3401

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/09/07 14:26

回答ありがとうございます。 >違います。 synchronized 自身には対象オブジェクトのメンバ変数に対するアクセス制御の意味はありません。 正直わかっていないと思うのですが、質問させてください。 しかし、インスタンスの同期を取るためにsynchlonizedブロックは使われると思うのですが、その際対象インスタンスに指定されたインスタンスの同期が取れるようになりますね? それってメンバへのアクセスを意味するのではないでしょうか? 私があげたコードだとkanojyoクラスのメソッドやフィールドにはロックを取得したスレッドしかアクセスできないと思ったのですが。 後半の回答を見ますと、対象インスタンスは何でもいいと思ってしまったのですが、そういうことなのでしょうか?
mit0223

2016/09/07 14:37

はい、Thread-1 が kanojyo をロックしている時でも、kanojyo の排他制御に参加しない(=synchronized しない)スレッドはメンバにアクセスできます。 Java の synchronized はアドバイザリロックです。解説を回答に追記しておきます。
mit0223

2016/09/07 14:58

すみません。さっきの回答は質問の回答になってなかったように思います。synchronized に指定するオブジェクトはなんでもいいです。アドバイザリロックなので、排他制御に参加するスレッド間でどのように約束するかという問題です。例のプログラムでは、kanojyoにアクセスするときは synchronized(kanojyo) するというのが約束であるということです。でも、kanojyoにアクセスするときは synchronized(kareshi) するという約束でも動作します。
mit0223

2016/09/07 15:22

ちょっと補足です。synchronized 漏れのバグを防ぐために、 synchronized するコードが分散しないようにする必要があります。なので、通常は、 synchronized(this)や synchronized メソッドを使うわけです。そういう意味では、 kanojyo の例も Display の例もコーディングが悪いです。kanojyo のロックは kanojyo 自身にとらせる(Kanojyo クラス側に synchronized のコードを入れる)べきですが、排他制御の様子をわかりやすくするために外からロックをとっちゃってます。
退会済みユーザー

退会済みユーザー

2016/09/08 13:47

丁寧にありがとうございます。 >アドバイザリロックなので、排他制御に参加するスレッド間でどのように約束するかという問題です。例のプログラムでは、kanojyoにアクセスするときは synchronized(kanojyo) するというのが約束であるということです。でも、kanojyoにアクセスするときは synchronized(kareshi) するという約束でも動作します。 対象インスタンスとしてthisを指定して実行すると、同期ができなくなったわけですが、これはどのようにして説明するのでしょうか? オブジェクトはkanojyoでなくてはならないのですよね?
mit0223

2016/09/08 14:10

おお、とっても良い質問です(Java 上級者コースです)。 new Thread(){ public void run(){ この書き方はいわゆる無名クラスです。この run のメソッド定義の中で this を参照すると、 Thread クラスの無名のサブクラスのインスタンス(ああ、ややこしい)を参照することになります。したがって、 Thread-0 と Thread-1 では自分のスレッドのインスタンスを参照することになり、別オブジェクトになります。別オブジェクトで synchronized しても排他制御になりません。アドバイザリロックで排他制御する場合は、スレッド間で共通の1個のオブジェクトに対して syncronized する必要があります。こういう間違いを減らすためにも Kanojo クラスのメソッドの中で synchronized(this) というように記述すべきです。
mit0223

2016/09/08 14:36

上級者コースすぎて、ここで解説しきれるかどうか・・・補足です。 無名クラスの中でメソッド定義(この場合 run メソッド)する場合、その自由変数(メソッド内でも引数でも宣言されてない変数で、この場合 kanojo )はその定義が出現するメソッド(ここでは main)内のローカル変数を参照できます。 つまり、Thread-0, Thread-1 を実行する run() 内の変数 kanojo は new Thread() が実行された時点のローカル変数 kanojo の値を参照します。 これは Java では無名クラスだけでできるわざです。1.8から何か新しいのが導入されて他の方法もありますが、今思い出せません。 JavaScript とかではクロージャという概念があって、このことが日常茶飯事で、それが JavaScript の優れた点であると言われてます。
退会済みユーザー

退会済みユーザー

2016/09/08 14:40

おかげさまで大方理解できたと思いますが、一点だけわからない点がありました。 対象インスタンスとしてシングルトンを指定するのとそうでないもの(例えばthis)とするのではどういう違いがあるのでしょうか? スレッドの側からすると、(無名クラスではなく、同一のインスタンスを参照する)thisが指定されようと、シングルトンが指定されようと排他制御が行われるわけで、違いはないように思えたのですが、そうではないのですよね?
退会済みユーザー

退会済みユーザー

2016/09/08 14:53

最後の補足に対して、「これはJavaでは無名クラスだけで出来る技です」とありますが、クラス内で定義していない変数を使える、つまり自由変数を使えるという点でおっしゃられていますか? 他のクラス(通常のクラスやローカルクラス、メンバクラス)ではそのクラス内で自由変数を使うとエラーになる、ということでまちがいないでしょうか??
mit0223

2016/09/08 14:58

また、良い質問です。マルチスレッド(特に複数のCPUが搭載されたコンピュータで並列に実行される場合)で性能を最大限に出そうとすることを考えてみましょう。シングルトンで1個のスレッドしかクリティカルセクションに入れない(=排他制御の対象処理を実行できない)のと、オブジェクトが違えば並列で処理できるのを比較すると後者のほうが処理を並列で実行できるので、性能が良いことになります。つまり、複数のインスタンスを持つクラスの排他制御について、インスタンスごとに排他制御するのであれば、 this を参照するほうが性能が良いことになります。 こういうのをロックの粒度といいます。たとえば、リレーショナルデータベースでは、データベースまるごとロックしたり、特定のテーブルをロックしたり、テーブルの中の特定のレコードをロックしたり、レコードの中の特定のフィールドをロックできたりします。さらにこれらを混ぜて利用する場合は複粒度ロッキングと言って技術的に真似するのはとてもむずかしくなります。
mit0223

2016/09/08 15:09

無名クラスでしか使えない技について解説を試みます。 クラスの static 変数→最初から最後まで存在 クラスのメンバ変数→インスタンスがある間のみ存在 メソッドのローカル変数→メソッドの実行中のみ存在 無名クラスのメソッド定義内の自由変数→インスタンスがある間のみ存在 で、自由変数の最大の特長は宣言がないのに自動的にクラスのメンバ変数になっていることです。このことをわざと言いました。 kanojo については、本来メソッドのローカル変数なのにメソッドが終了しても参照できます。それは、実はメソッドのローカル変数を参照しているのではなく、 new Thread を実行した時点で暗黙のメンバ変数が作られているという意味付けになってるからなんです。
mit0223

2016/09/08 15:21

すみません。私の悪い癖で質問に答えてませんでした。 > 他のクラス(通常のクラスやローカルクラス、メンバクラス)ではそのクラス内で自由変数を使うとエラーになる、ということでまちがいないでしょうか?? はい、自由変数は宣言されていない変数という意味で、これでまちがいありません。
退会済みユーザー

退会済みユーザー

2016/09/09 13:48

回答ありがとうございます。 おかげで対象インスタンスや排他制御については理解できました。 さて、後半の無名クラスの特徴についてですが、 >kanojo については、本来メソッドのローカル変数なのにメソッドが終了しても参照できます。それは、実はメソッドのローカル変数を参照しているのではなく、 new Thread を実行した時点で暗黙のメンバ変数が作られているという意味付けになってるからなんです。 後半のこの文章がよく理解できませんでした。 kanojyoは本来メソッドのローカル変数であると言われていますが、ここでいうメソッドはどのメソッドでしょうか? 無名クラスの中のメソッドでしょうか? それとも無名クラスの定義がなされるメソッドでしょうか?
mit0223

2016/09/11 07:40

わかりにくくてすみません。変数 kanojo は main メソッドのローカル変数なので、 main メソッドが終了した時点で変数は消えます。 しかし、2つの Thread の無名サブクラスのrun メソッドの中からは main が終了した後でも kanojo が参照できています。これは、暗黙的に無名サブクラスのメンバ変数となってインスタンスに値がコピーされていると解釈すれば良いでしょうということです。 ついでに「1.8から何か新しいのが導入されて他の方法もありますが、今思い出せません」は「ラムダ式」でした。あと、Java のこの技法では対象の変数が final でなければならず、厳密にはクロージャではありません。JavaScript で同様に外側のローカル変数を自由変数で参照すると値を変更できたりします。
退会済みユーザー

退会済みユーザー

2016/09/11 14:19

回答ありがとうございます。 自由変数を書きかえることのできる手段として、無名クラスやラムダ式があることは理解できたのですが、ローカルクラス(メソッド内で宣言するクラス)はこれらの仲間とは言えないのでしょうか? と言いますのも、ローカルクラスはその定義がなされているメソッド内のfinalが宣言された変数及び定義されているクラスのすべての変数にアクセスできます。
退会済みユーザー

退会済みユーザー

2016/09/12 01:38

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

2016/09/12 02:18

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

退会済みユーザー

2016/09/12 11:06

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

0

あるオブジェクトのロックを取得することと、そのオブジェクト内部へのアクセス権を取得することは全くの無関係です。
例えるなら次のような状況です。
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/07 15:40

swordone

総合スコア20651

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/09/08 13:57

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

2016/09/08 15:23

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問