クラス初期化によるデッドロック
11843 ワード
1.デッドロックはどのように発生したのか
クラスの初期化は隠れた操作であり、仮想マシンが主導して完成している.開発者がクラスのロードメカニズムを理解していないと、クラスの初期化が何なのか分からないかもしれない.クラスの初期化については、Java仮想マシンクラスのロードメカニズムを参照してください.詳細は説明されています.クラスの初期化にはいくつかの重要な機能があります.クラスの初期化プロセスは、実際にはクラスコンストラクタメソッド()を実行するプロセスである. サブクラスの初期化が完了すると、仮想機会はその親クラスの初期化が完了することを保証する. マルチスレッド環境では、仮想マシンが()メソッドを実行すると自動的にロックされます.
Javaでは、デッドロックはマルチスレッド環境で発生するに違いありません.複数のスレッドが同時に互いに持つリソースが必要で、自分のリソースが解放されず、他の人のリソースが得られず、循環依存をもたらし、さらにそこにブロックされ続け、デッドロックになる.
2.デッドロックが発生した場合
2.1クラス初期化相互依存
最も明らかなのは、2つのクラスが異なるスレッドで初期化され、互いに依存していることです.例を見てみましょう.
実行結果は次のとおりです.
1番目のスレッドがA.test()を実行すると、クラスAが初期化され、そのスレッドがA.classのロックを取得し、2番目のスレッドがB.test()を実行すると、クラスBが初期化され、そのスレッドがB.classのロックを取得する.Aが初期化中にコードB b=new B()を実行すると、クラスBはまだ初期化が完了していないことが判明し、クラスB.classのロックを取得しようとする.クラスBは初期化時にコードA a=new A()を実行し、クラスAも初期化が完了していないことを発見し、クラスA.classのロックを取得しようとしたが、A.classロックが占有されているため、スレッドがブロックされ、ロックの解放を待つ.同じように最初のスレッドがブロックされ、B.classロックの解放を待つと、ループ依存性が発生し、デッドロックが形成される.
上のコードを次のように実行すると、どのような結果になりますか?
一見、Aが初期化されるとBに依存し、Bが初期化されるとAに依存し、デッドロックを起こすようですが、実際にはそうではありません.A、Bの2つのクラスの初期化はすべて同じスレッドで実行され、Aを初期化すると、そのスレッドはA.classロックを取得し、Bを初期化するとB.classロックを取得し、Bを初期化する際にはAを必要とするが、この2つの初期化はいずれも同じスレッドで実行され、このスレッドは同時にこの2つのロックを取得するので、ロックリソースのプリエンプトは発生しない.最終的な実行結果は次のとおりです.
2.2子、親初期化デッドロック
1つ目のケースに比べて、このような状況によるデッドロックはより隠れていますが、実質的には同じ原因で、具体的な例を見てみましょう.
実行結果:
デッドロックの原因を分析します.1.スレッドt 1が実行されるとChildクラスの初期化がトリガーされ、スレッドt 2が実行されるとParentクラスの初期化がトリガーされる.2.スレッドt 1に続くChildを持つ.classロック、t 2はParentを持つ.classロックは、t 1の初期化には親パラメータを初期化する必要がありますが、クラスパラメータには「public static final Parent EMPTY=new Child();」という定数定義があります.このようなパラメータは初期化時にChildを初期化する必要がある.3.このようにスレッドt 1がParentを初期化する、Parentを取得しようとする.classロック、スレッドt 2はChildを初期化し、Childを取得しようとする.classロックは、互いにリソースを解放できないため、デッドロックをもたらします.
3.デッドロックによる血液検査
かつて開発されたAndroidプロジェクトでは、オープンソースのORMデータベースフレームワークlitepalを採用してデータベース操作を行った結果、アプリケーションがオンラインになった後、時々カード死が発生するというユーザーのフィードバックがしばしばあった.その後、自分のテストを経て、偶発的にカードが死ぬ現象もありますが、少しも規則がなく、バグの位置を特定することができず、ユーザーから苦情を受けて罵倒されたのは惨めで、開発者を焦らせました.その後、携帯電話のanrファイルをエクスポートし、よく分析したところ、litepalデータベースにデッドロックが発生したため、anrが現れたことが分かった.(注:litepal自体は使いやすいAndroid ORMデータベースフレームワークで、ほとんどの場合使いやすいですが、ここでは私たちの使用シーンを説明します.)
クラスの初期化は隠れた操作であり、仮想マシンが主導して完成している.開発者がクラスのロードメカニズムを理解していないと、クラスの初期化が何なのか分からないかもしれない.クラスの初期化については、Java仮想マシンクラスのロードメカニズムを参照してください.詳細は説明されています.クラスの初期化にはいくつかの重要な機能があります.
Javaでは、デッドロックはマルチスレッド環境で発生するに違いありません.複数のスレッドが同時に互いに持つリソースが必要で、自分のリソースが解放されず、他の人のリソースが得られず、循環依存をもたらし、さらにそこにブロックされ続け、デッドロックになる.
2.デッドロックが発生した場合
2.1クラス初期化相互依存
最も明らかなのは、2つのクラスが異なるスレッドで初期化され、互いに依存していることです.例を見てみましょう.
public class Test {
public static class A {
static {
System.out.println("class A init.");
B b = new B();
}
public static void test() {
System.out.println("method test called in class A");
}
}
public static class B {
static {
System.out.println("class B init.");
A a = new A();
}
public static void test() {
System.out.println("method test called in class B");
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
A.test();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
B.test();
}
}).start();
}
}
実行結果は次のとおりです.
class A init.
class B init.
1番目のスレッドがA.test()を実行すると、クラスAが初期化され、そのスレッドがA.classのロックを取得し、2番目のスレッドがB.test()を実行すると、クラスBが初期化され、そのスレッドがB.classのロックを取得する.Aが初期化中にコードB b=new B()を実行すると、クラスBはまだ初期化が完了していないことが判明し、クラスB.classのロックを取得しようとする.クラスBは初期化時にコードA a=new A()を実行し、クラスAも初期化が完了していないことを発見し、クラスA.classのロックを取得しようとしたが、A.classロックが占有されているため、スレッドがブロックされ、ロックの解放を待つ.同じように最初のスレッドがブロックされ、B.classロックの解放を待つと、ループ依存性が発生し、デッドロックが形成される.
上のコードを次のように実行すると、どのような結果になりますか?
public static void main(String[] args) {
A.test();
B.test();
}
一見、Aが初期化されるとBに依存し、Bが初期化されるとAに依存し、デッドロックを起こすようですが、実際にはそうではありません.A、Bの2つのクラスの初期化はすべて同じスレッドで実行され、Aを初期化すると、そのスレッドはA.classロックを取得し、Bを初期化するとB.classロックを取得し、Bを初期化する際にはAを必要とするが、この2つの初期化はいずれも同じスレッドで実行され、このスレッドは同時にこの2つのロックを取得するので、ロックリソースのプリエンプトは発生しない.最終的な実行結果は次のとおりです.
class A init.
class B init.
method test called in class A
method test called in class B
2.2子、親初期化デッドロック
1つ目のケースに比べて、このような状況によるデッドロックはより隠れていますが、実質的には同じ原因で、具体的な例を見てみましょう.
public class Test {
public static class Parent {
static {
System.out.println("Parent init.");
}
public static final Parent EMPTY = new Child();
public static void test() {
System.out.println("test called in class Parent.");
}
}
public static class Child extends Parent {
static {
System.out.println("Child init.");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Child c = new Child();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Parent.test();
}
});
t1.start();
t2.start();
}
}
実行結果:
Parent init.
デッドロックの原因を分析します.1.スレッドt 1が実行されるとChildクラスの初期化がトリガーされ、スレッドt 2が実行されるとParentクラスの初期化がトリガーされる.2.スレッドt 1に続くChildを持つ.classロック、t 2はParentを持つ.classロックは、t 1の初期化には親パラメータを初期化する必要がありますが、クラスパラメータには「public static final Parent EMPTY=new Child();」という定数定義があります.このようなパラメータは初期化時にChildを初期化する必要がある.3.このようにスレッドt 1がParentを初期化する、Parentを取得しようとする.classロック、スレッドt 2はChildを初期化し、Childを取得しようとする.classロックは、互いにリソースを解放できないため、デッドロックをもたらします.
3.デッドロックによる血液検査
かつて開発されたAndroidプロジェクトでは、オープンソースのORMデータベースフレームワークlitepalを採用してデータベース操作を行った結果、アプリケーションがオンラインになった後、時々カード死が発生するというユーザーのフィードバックがしばしばあった.その後、自分のテストを経て、偶発的にカードが死ぬ現象もありますが、少しも規則がなく、バグの位置を特定することができず、ユーザーから苦情を受けて罵倒されたのは惨めで、開発者を焦らせました.その後、携帯電話のanrファイルをエクスポートし、よく分析したところ、litepalデータベースにデッドロックが発生したため、anrが現れたことが分かった.(注:litepal自体は使いやすいAndroid ORMデータベースフレームワークで、ほとんどの場合使いやすいですが、ここでは私たちの使用シーンを説明します.)
"main" tid=1 :
| group="main" sCount=1 dsCount=0 obj=0x757e6598 self=0xab361100
| sysTid=17006 nice=0 cgrp=default sched=0/0 handle=0xf7210b50
| state=S schedstat=( 731900052 38102591 941 ) utm=53 stm=20 core=6 HZ=100
| stack=0xff0dc000-0xff0de000 stackSize=8MB
| held mutexes=
at org.litepal.crud.DataSupport.findFirst(DataSupport.java:-1)
- waiting to lock <0x005e5028> (a java.lang.Class) held by thread 27
at ......
"RxCachedThreadScheduler-2" tid=27 :
| group="main" sCount=1 dsCount=0 obj=0x12e751c0 self=0xab9ae8a8
| sysTid=17097 nice=0 cgrp=default sched=0/0 handle=0xdbb46930
| state=S schedstat=( 548637659 14253750 564 ) utm=50 stm=4 core=3 HZ=100
| stack=0xdba44000-0xdba46000 stackSize=1038KB
| held mutexes=
kernel: (couldn't read /proc/self/task/17097/stack)
native: #00 pc 00016998 /system/lib/libc.so (syscall+28)
native: #01 pc 000f5e73 /system/lib/libart.so (_ZN3art17ConditionVariable4WaitEPNS_6ThreadE+82)
native: #02 pc 002ae8b3 /system/lib/libart.so (_ZN3art7Monitor4LockEPNS_6ThreadE+394)
native: #03 pc 002b140f /system/lib/libart.so (_ZN3art7Monitor12MonitorEnterEPNS_6ThreadEPNS_6mirror6ObjectE+266)
native: #04 pc 002e5747 /system/lib/libart.so (_ZN3art10ObjectLockINS_6mirror6ObjectEEC2EPNS_6ThreadENS_6HandleIS2_EE+22)
native: #05 pc 00139bab /system/lib/libart.so (_ZN3art11ClassLinker15InitializeClassEPNS_6ThreadENS_6HandleINS_6mirror5ClassEEEbb.part.593+90)
native: #06 pc 0013aa97 /system/lib/libart.so (_ZN3art11ClassLinker17EnsureInitializedEPNS_6ThreadENS_6HandleINS_6mirror5ClassEEEbb+82)
native: #07 pc 002bd76d /system/lib/libart.so (_ZN3artL18Class_classForNameEP7_JNIEnvP7_jclassP8_jstringhP8_jobject+292)
native: #08 pc 0024eca9 /system/framework/arm/boot.oat (Java_java_lang_Class_classForName__Ljava_lang_String_2ZLjava_lang_ClassLoader_2+132)
at java.lang.Class.classForName!(Native method)
- waiting to lock <0x0229fe4b> (a java.lang.Class<......database.announcementinfo>) held by thread 36
at java.lang.Class.forName(Class.java:324)
at java.lang.Class.forName(Class.java:285)
"RxCachedThreadScheduler-4" tid=36 :
| group="main" sCount=1 dsCount=0 obj=0x12c3ce80 self=0xab8ab088
| sysTid=17229 nice=0 cgrp=default sched=0/0 handle=0xdab2b930
| state=S schedstat=( 56642965 8922138 61 ) utm=4 stm=1 core=6 HZ=100
| stack=0xdaa29000-0xdaa2b000 stackSize=1038KB
| held mutexes=
kernel: (couldn't read /proc/self/task/17229/stack)
native: #00 pc 00016998 /system/lib/libc.so (syscall+28)
native: #01 pc 000f5e73 /system/lib/libart.so (_ZN3art17ConditionVariable4WaitEPNS_6ThreadE+82)
native: #02 pc 002ae8b3 /system/lib/libart.so (_ZN3art7Monitor4LockEPNS_6ThreadE+394)
native: #03 pc 002b140f /system/lib/libart.so (_ZN3art7Monitor12MonitorEnterEPNS_6ThreadEPNS_6mirror6ObjectE+266)
native: #04 pc 002e5747 /system/lib/libart.so (_ZN3art10ObjectLockINS_6mirror6ObjectEEC2EPNS_6ThreadENS_6HandleIS2_EE+22)
native: #05 pc 00139165 /system/lib/libart.so (_ZN3art11ClassLinker11VerifyClassEPNS_6ThreadENS_6HandleINS_6mirror5ClassEEE+336)
native: #06 pc 00139c0d /system/lib/libart.so (_ZN3art11ClassLinker15InitializeClassEPNS_6ThreadENS_6HandleINS_6mirror5ClassEEEbb.part.593+188)
native: #07 pc 0013aa97 /system/lib/libart.so (_ZN3art11ClassLinker17EnsureInitializedEPNS_6ThreadENS_6HandleINS_6mirror5ClassEEEbb+82)
native: #08 pc 002cdb8b /system/lib/libart.so (_ZN3artL23Constructor_newInstanceEP7_JNIEnvP8_jobjectP13_jobjectArray+134)
native: #09 pc 0024f0cd /system/framework/arm/boot.oat (Java_java_lang_reflect_Constructor_newInstance___3Ljava_lang_Object_2+96)
at java.lang.reflect.Constructor.newInstance!(Native method)
- waiting to lock <0x005e5028> (a java.lang.Class) held by thread 27
at com.google.gson.internal.ConstructorConstructor$3.construct(ConstructorConstructor.java:-1)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:-1)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:-1)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:-1)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:-1)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:-1)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:-1)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:-1)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:-1)
at com.google.gson.Gson.fromJson(Gson.java:-1)
at com.google.gson.Gson.fromJson(Gson.java:-1)
at com.google.gson.Gson.fromJson(Gson.java:-1)
ここではanrファイルの を り りました. から かるように、スレッドt 1はDataSupportを している.findFirst()メソッドの 、DataSupportが です.Classロック、DataSupport.classロックはスレッドt 27によって されるため、t 1はずっとブロックされており、t 1はメインスレッドであるため、メインスレッドがブロックされるためanr が する.スレッドt 27を てみると、AnnouncementInfoが です.classロックは、スレッドt 36によって される. にスレッドt 36を ると、DataSupportロックが であることが かった.ここを ると、 にデッドロックが していることがわかります.
DataSupportはlitepalフレームワークで されたデータベース ベースクラスです.AnnouncementInfoは たちが したデータテーブルクラスです.DataSupportクラスから する があります. を てみましょう.// AnnouncementInfo
public class AnnouncementInfo extends DataSupport {
//
}
DataSupportのfindFirst()メソッドの :public static synchronized T findFirst(Class modelClass);
たちのアプリケーションでは、データベースを する に び し を するいくつかの なるデータテーブルが されています.AnnouncementInfoデータテーブルのクエリの として、 は のように かれています.AnnouncementInfo data = DataSupport.findFirst(AnnouncementInfo.class);
このように するのは ありませんが、データベーステーブルを で し、 のサブスレッドでAnnouncementInfoクラスを すると、 が します.メインスレッドはDataSupportを する.findFirstメソッドの 、DataSupportクラスが されていないことが した は、まずDataSupportの を みる.classロックは、ロックが された にのみ されます.2.あるサブスレッドがデータベース にDataSupportクラスの をトリガし、 にAnnouncementInfoクラスに していることが するが、AnnouncementInfoクラスはこの で されていないため、AnnouncementInfoを しようとする.クラスロックはクラスを します.3. に、あるサブスレッドがGsonライブラリ jsonデータを いてAnnouncementInfoオブジェクトインスタンスを すると、AnnouncementInfoクラスの がトリガーされるが、AnnouncementInfoクラスの には のDataSupportを する があり、2 のステップでDataSupportクラスの がブロックされている.これによりサイクル が じ, スレッドのブロックがanrを き こす.
4.デッドロック
の では、クラスの にデッドロックが したことを っています.サブクラスは に し、 は にサブクラスに するため、このような を するために、データベース クラスを にプライマリ・スレッドですべて する を しました.アプリケーションの り で、 のように しました.Class c1 = Class.forName("AnnouncementInfo");
Class c2 = Class.forName("......");
......
これにより、アプリケーションが すると、すべてのデータベース クラスが され、データベースを で すると、 のデッドロックは と しません.
5.まとめ
に、コードにデッドロックが すると、 にマルチスレッド では、 に が です.しかし,デッドロックの な を すれば, の ではほぼ できる.
JAvaクラスロードメカニズムシリーズの : Java マシンクラスロードメカニズム Java Classファイル によるデッドロック Javaクラスローダ