Java Concurrency in Practiceにおけるオブジェクトロック再入問題の理解
13009 ワード
理由:Java Concurrency in Practice中国語版21ページで対象ロックの再入について説明していましたが、作者からの例はなかなか読めませんでした.今日は長い間考えていましたが、自分を説得できる理由を見つけました.
1原書の内容は以下の通りである.
あるスレッドが他のスレッドが持つロックを要求すると、要求を発行するスレッドがブロックされます.しかしながら、内蔵ロックは再入力可能であるため、スレッドに触って自分で所有しているロックを取得しようとすると、この要求は成功する.「再読み込み」は、ロックを取得する操作の粒度が呼び出しではなく「スレッド」であることを意味します.再読み込みの実装方法の1つは、各ロックにカウント値と所有者スレッドを関連付けます.カウント値が0の場合、このロックはスレッドによって所有されていないとみなされ、スレッドが未所有のロックを要求すると、JVMはロックの所有者をメモし、取得カウント値を1にし、同じスレッドが再びこのロックを取得するとカウント値が増加し、スレッドが同期コードブロックを終了すると、カウンタはそれに応じて減少する.カウント値が0の場合、このロックは解放されます.再入はさらにロック挙動のパッケージング性を向上させるため,オブジェクト向けの同時コードの開発を簡素化した.次の手順で分析します.
サブクラスは、親の同期メソッドを上書きし、親のメソッドを呼び出します.このとき、再読み込み可能なロックがない場合、このコードはデッドロックを生成します.FatherメソッドとChildのdoSomethingメソッドはsynchronizedメソッドであるため、各doSomethingメソッドは実行前にChildオブジェクトインスタンス上のロックを取得します.内蔵ロックが再入可能でない場合はsuperを呼び出す.doSomethingではChildオブジェクトの反発ロックは取得できません.このロックはすでに保持されているため、スレッドは永遠にブロックされ、永遠に取得できないロックを待っています.再入はこのようなデッドロックの発生を回避した.
2私の質問は次のとおりです.
正直に言うと、この本を読むとき、私は学生が作者に対してこの上ない崇敬の気持ちに基づいて恐る恐る鑑賞していて、自分が読めないのではないかと心配していました.作者の文字の内容を見終わった後、認めと感心のほかに、何の疑問も生じなかった.しかし、著者のサンプルコードを見ると、私は全然分からず、気持ちが落ち込んでいました.私は一度作者のレベルに疑問を抱いたが、作者の問題ではないと思った.自分のレベルがどれだけ低いか、私の問題だと知っていたからだ.疑わしいのは、スレッドt 1がChildlクラスのあるインスタンスc 1のdoSomethingメソッドを呼び出すと仮定すると、正常に呼び出される前にt 1がc 1のロックを取得するべきである.次にFather(super:c 1を作成するには親クラスのインスタンスf 1を作成し、superをf 1の参照とする)クラスのあるインスタンスf 1のdoSomethingメソッドを呼び出す必要があることはよく知られていますが、呼び出しに成功する前にt 1はまずf 1のロックを取得する必要があります.つまり、t 1スレッドが前後したり、2つの異なるオブジェクトのロックを取得したりするのは、どのように再入と呼ぶことができますか.
3私の探求は以下の通りです.
1.第一歩探索
予測結果:the mysomething method of father the dosomething method of son 3秒後に次の内容を印刷the dosomething method of father
予測結果分析:t 1はsonを呼び出す.mydosomethingメソッドではs 1オブジェクトのロックを取得する必要はありませんが、son.mydosomethingメソッドでsuperが呼び出されました.mydosomething()メソッドは、f 1インスタンスのロックを取得し、「the mysomething method of father」を印刷し、3秒間停止します.メインスレッドが100ミリ秒停止するとs 1が実行される.dosomethingメソッド、この方法では、s 1インスタンスのロック(t 1がs 1インスタンスのロックを取得していないため、成功した)を取得し、「the dosomething method of son」を印刷し、次にsuper.dosomethingを呼び出し、f 1インスタンスのロックを取得する必要がある(取得に失敗し、f 1がt 1で取得され、3秒後にf 1のロックが解放される)、メインスレッドがブロックされ、3秒後に「the dosomething method of father」を印刷する
真実の結果:the mysomething method of father 3秒後に次の内容を印刷the dosomething method of son the dosomething method of father結果分析:真実の結果を見て、自分が馬鹿で無知だと思った.真実の結果を前にして、私は真実の味を感じたようです:t 1が実行しているようです_Fatherクラスにおけるmydosomethingメソッドの場合に得られるインスタンスs 1のロックはf 1のロックではなく,すなわち著者らの言うとおりである.
2.第二歩探求今度はいっそやらないで、直接_JavaConcurrency_01には、すべてのsuperの代わりにFatherクラスのインスタンスf 1が作成される.
予測結果:the mysomething method of father the dosomething method of son 3秒後に次の内容を印刷the dosomething method of father
予測結果分析:第一歩探索と同様
本当の結果:予測結果と完全に一致しているので、私の言い方が間違っているようで、本当に自分の顔を殴りたいです.
3.第三歩探索s 1.mydosomething() ->super.mydosomething()今回私が探しているのはsuperです.mydosomething()メソッド呼び出しの場合、デフォルトでは「this」パラメータが渡され、thisはこのメソッドを呼び出したインスタンスを指します.私はこのデフォルトのthisがs 1を指しているのか、それとも私が言ったf 1を指しているのかを見たいだけです.
ここで私はもう予測しないで、人は自覚の明があることを学ばなければならなくて、直接結果に行って、結果はとても恐ろしくて、少なくとも私はこのように感じて、あまりにも無知で、少しも驚くことに耐えられません.
本当の結果は?mydosomething()でデフォルトの「this」パラメータはs 1を指しています.なんてことだ.作者の言うことは少しも間違っていません.本当に再入力します.
4.第4歩では、javap-verbose Childコマンドを利用して、バイトコードコマンドがどのように実行されているかを見てみましょう.
バイトコードコマンド記述:0-7行がChildタイプのインスタンス、すなわちs 1を作成する.astore_1は、オペランドスタックトップ参照タイプの数値をローカル変数テーブルの2番目(ゼロからカウント)のローカル変数位置、すなわちs 1をローカル変数テーブルの1番目の位置に格納する.aload_1は、s 1をスタックトップに押し込む.invokevirtualは、インスタンスメソッドdoSomethingを実行し、このときに渡される「this」がスタックトップ要素s 1となる.
ここで重要なdoSomethingメソッド0:aload_を見てみましょう0,thisをスタックトップに押し込み,この「this」はs 1の参照である.1: invokespecial #15; 親メソッドを呼び出しますが、デフォルトで入力されたthisパラメータはs 1を指します.
4私の無知を許してください.
最后に、私は1つの自分を说得する理由を探し当てて、しかしその中にやはり多くの问题があって、それがなぜそうなのか分からないことを知っていて、未来の日の中でゆっくりとこれらの问题を解决することができることを望んで、自分に学问が起きさせて、はは.
1原書の内容は以下の通りである.
あるスレッドが他のスレッドが持つロックを要求すると、要求を発行するスレッドがブロックされます.しかしながら、内蔵ロックは再入力可能であるため、スレッドに触って自分で所有しているロックを取得しようとすると、この要求は成功する.「再読み込み」は、ロックを取得する操作の粒度が呼び出しではなく「スレッド」であることを意味します.再読み込みの実装方法の1つは、各ロックにカウント値と所有者スレッドを関連付けます.カウント値が0の場合、このロックはスレッドによって所有されていないとみなされ、スレッドが未所有のロックを要求すると、JVMはロックの所有者をメモし、取得カウント値を1にし、同じスレッドが再びこのロックを取得するとカウント値が増加し、スレッドが同期コードブロックを終了すると、カウンタはそれに応じて減少する.カウント値が0の場合、このロックは解放されます.再入はさらにロック挙動のパッケージング性を向上させるため,オブジェクト向けの同時コードの開発を簡素化した.次の手順で分析します.
public class Father
{
public synchronized void doSomething(){
......
}
}
public class Child extends Father
{
public synchronized void doSomething(){
......
super.doSomething();
}
}
サブクラスは、親の同期メソッドを上書きし、親のメソッドを呼び出します.このとき、再読み込み可能なロックがない場合、このコードはデッドロックを生成します.FatherメソッドとChildのdoSomethingメソッドはsynchronizedメソッドであるため、各doSomethingメソッドは実行前にChildオブジェクトインスタンス上のロックを取得します.内蔵ロックが再入可能でない場合はsuperを呼び出す.doSomethingではChildオブジェクトの反発ロックは取得できません.このロックはすでに保持されているため、スレッドは永遠にブロックされ、永遠に取得できないロックを待っています.再入はこのようなデッドロックの発生を回避した.
2私の質問は次のとおりです.
正直に言うと、この本を読むとき、私は学生が作者に対してこの上ない崇敬の気持ちに基づいて恐る恐る鑑賞していて、自分が読めないのではないかと心配していました.作者の文字の内容を見終わった後、認めと感心のほかに、何の疑問も生じなかった.しかし、著者のサンプルコードを見ると、私は全然分からず、気持ちが落ち込んでいました.私は一度作者のレベルに疑問を抱いたが、作者の問題ではないと思った.自分のレベルがどれだけ低いか、私の問題だと知っていたからだ.疑わしいのは、スレッドt 1がChildlクラスのあるインスタンスc 1のdoSomethingメソッドを呼び出すと仮定すると、正常に呼び出される前にt 1がc 1のロックを取得するべきである.次にFather(super:c 1を作成するには親クラスのインスタンスf 1を作成し、superをf 1の参照とする)クラスのあるインスタンスf 1のdoSomethingメソッドを呼び出す必要があることはよく知られていますが、呼び出しに成功する前にt 1はまずf 1のロックを取得する必要があります.つまり、t 1スレッドが前後したり、2つの異なるオブジェクトのロックを取得したりするのは、どのように再入と呼ぶことができますか.
3私の探求は以下の通りです.
1.第一歩探索
class _Father{
public synchronized void dosomething(){
System.out.println("the dosomething method of father");
}
public synchronized void mydosomething() throws InterruptedException{
System.out.println("the mysomething method of father");
Thread.sleep(3000);
}
}
public class _JavaConcurrency_01 extends _Father{
public synchronized void dosomething() {
System.out.println("the dosomething method of son");
super.dosomething();
}
public void mydosomething() throws InterruptedException {
super.mydosomething();
}
public static void main(String[] args) throws InterruptedException {
final _JavaConcurrency_01 s1 = new _JavaConcurrency_01();
// t1 son.mydosomething ( )
// super.mydosomething , f1(f1 ) , t1 3
new Thread(new Runnable() {
public void run() {
try {
s1.mydosomething();
} catch (InterruptedException e) {
}
}
}, "t1").start();
// t1
Thread.sleep(100);
// son.dosomething f1 dosomething ,f1 t1 , t1
s1.dosomething();
}
}
予測結果:the mysomething method of father the dosomething method of son 3秒後に次の内容を印刷the dosomething method of father
予測結果分析:t 1はsonを呼び出す.mydosomethingメソッドではs 1オブジェクトのロックを取得する必要はありませんが、son.mydosomethingメソッドでsuperが呼び出されました.mydosomething()メソッドは、f 1インスタンスのロックを取得し、「the mysomething method of father」を印刷し、3秒間停止します.メインスレッドが100ミリ秒停止するとs 1が実行される.dosomethingメソッド、この方法では、s 1インスタンスのロック(t 1がs 1インスタンスのロックを取得していないため、成功した)を取得し、「the dosomething method of son」を印刷し、次にsuper.dosomethingを呼び出し、f 1インスタンスのロックを取得する必要がある(取得に失敗し、f 1がt 1で取得され、3秒後にf 1のロックが解放される)、メインスレッドがブロックされ、3秒後に「the dosomething method of father」を印刷する
真実の結果:the mysomething method of father 3秒後に次の内容を印刷the dosomething method of son the dosomething method of father結果分析:真実の結果を見て、自分が馬鹿で無知だと思った.真実の結果を前にして、私は真実の味を感じたようです:t 1が実行しているようです_Fatherクラスにおけるmydosomethingメソッドの場合に得られるインスタンスs 1のロックはf 1のロックではなく,すなわち著者らの言うとおりである.
2.第二歩探求今度はいっそやらないで、直接_JavaConcurrency_01には、すべてのsuperの代わりにFatherクラスのインスタンスf 1が作成される.
class _Father{
public synchronized void dosomething(){
System.out.println("the dosomething method of father");
}
public synchronized void mydosomething() throws InterruptedException{
System.out.println("the mysomething method of father");
Thread.sleep(3000);
}
}
public class _JavaConcurrency_01 extends _Father{
_Father f1 = new _Father();
public synchronized void dosomething() {
System.out.println("the dosomething method of son");
f1.dosomething();
}
public void mydosomething() throws InterruptedException {
f1.mydosomething();
}
public static void main(String[] args) throws InterruptedException {
final _JavaConcurrency_01 s1 = new _JavaConcurrency_01();
new Thread(new Runnable() {
public void run() {
try {
s1.mydosomething();
} catch (InterruptedException e) {
}
}
}, "t1").start();
Thread.sleep(100);
s1.dosomething();
}
}
予測結果:the mysomething method of father the dosomething method of son 3秒後に次の内容を印刷the dosomething method of father
予測結果分析:第一歩探索と同様
本当の結果:予測結果と完全に一致しているので、私の言い方が間違っているようで、本当に自分の顔を殴りたいです.
3.第三歩探索s 1.mydosomething() ->super.mydosomething()今回私が探しているのはsuperです.mydosomething()メソッド呼び出しの場合、デフォルトでは「this」パラメータが渡され、thisはこのメソッドを呼び出したインスタンスを指します.私はこのデフォルトのthisがs 1を指しているのか、それとも私が言ったf 1を指しているのかを見たいだけです.
class Father {
public void doSomething() {
System.out.print(this);
}
public String toString() {
return "Father";
}
}
public class Child extends Father {
public void doSomething() {
super.doSomething();
}
public String toString() {
return "Son";
}
public static void main(String[] args) {
Child child = new Child();
child.doSomething();
}
}
ここで私はもう予測しないで、人は自覚の明があることを学ばなければならなくて、直接結果に行って、結果はとても恐ろしくて、少なくとも私はこのように感じて、あまりにも無知で、少しも驚くことに耐えられません.
本当の結果は?mydosomething()でデフォルトの「this」パラメータはs 1を指しています.なんてことだ.作者の言うことは少しも間違っていません.本当に再入力します.
4.第4歩では、javap-verbose Childコマンドを利用して、バイトコードコマンドがどのように実行されているかを見てみましょう.
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=2, Args_size=1
0: new #1; //class _1/Child
3: dup
4: invokespecial #23; //Method "":()V
7: astore_1
8: aload_1
9: invokevirtual #24; //Method doSomething:()V
12: return
バイトコードコマンド記述:0-7行がChildタイプのインスタンス、すなわちs 1を作成する.astore_1は、オペランドスタックトップ参照タイプの数値をローカル変数テーブルの2番目(ゼロからカウント)のローカル変数位置、すなわちs 1をローカル変数テーブルの1番目の位置に格納する.aload_1は、s 1をスタックトップに押し込む.invokevirtualは、インスタンスメソッドdoSomethingを実行し、このときに渡される「this」がスタックトップ要素s 1となる.
public void doSomething();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #15; //Method _1/Father.doSomething:()V
4: return
ここで重要なdoSomethingメソッド0:aload_を見てみましょう0,thisをスタックトップに押し込み,この「this」はs 1の参照である.1: invokespecial #15; 親メソッドを呼び出しますが、デフォルトで入力されたthisパラメータはs 1を指します.
4私の無知を許してください.
最后に、私は1つの自分を说得する理由を探し当てて、しかしその中にやはり多くの问题があって、それがなぜそうなのか分からないことを知っていて、未来の日の中でゆっくりとこれらの问题を解决することができることを望んで、自分に学问が起きさせて、はは.