Javaの列挙型はどのようにスレッドセーフですか? (Blochの効果的なJavaに従って)enumを使用してシングルトンを実装していますが、シングルトンenumのスレッドセーフティについて心配する必要はありますか?スレッドセーフであることを証明または反証する方法はありますか?
// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
ありがとう
@Mikeが言っているように、列挙型の作成はスレッドセーフであることが保証されています。ただし、enumクラスに追加するメソッドは、スレッドの安全性を保証しません。特に、メソッドleaveTheBuilding
は、複数のスレッドによって同時に実行できます。このメソッドに副作用がある場合(一部の変数の状態を変更する場合)は、それを保護する(つまり、synchronized
にする)またはその一部を考慮する必要があります。
カスタマイズされた列挙定義はスレッドセーフではない可能性があります。例えば、
RoleEnum.Java:
package com.threadsafe.bad;
public enum RoleEnum {
ADMIN(1),
DEV(2),
HEAD(3);
private Integer value;
private RoleEnum(Integer role){
this.value=role;
}
public static RoleEnum fromIntegerValue(Integer role){
for(RoleEnum x : values()){
if(x.value == role ){
return x;
}
}
return RoleEnum.HEAD;
}
Class<?> buildFromClass;
public void setBuildFromClass(Class<?> classType){
buildFromClass=classType;
}
public Class<?> getBuildFromClass(){
return this.buildFromClass;
}
}
Main.Java:
package com.threadsafe.bad;
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread threadA = new Thread(){
public void run(){
System.out.println("A started");
RoleEnum role;
role=RoleEnum.fromIntegerValue(1);
System.out.println("A called fromIntegerValue");
role.setBuildFromClass(String.class);
System.out.println("A called setBuildFromClass and start to sleep");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Thread A: "+role.getBuildFromClass());
}
};
Thread threadB = new Thread(){
public void run(){
System.out.println("B started");
RoleEnum role;
role=RoleEnum.fromIntegerValue(1);
role.setBuildFromClass(Integer.class);
System.out.println("B called fromIntegerValue&setBuildFromClass and Start to sleep");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("B waked up!");
System.out.println("Thread B: "+ role.getBuildFromClass());
}
};
threadA.start();
threadB.start();
}
}
出力は時々:
Bが始めた
BがfromIntegerValue&setBuildFromClassを呼び出してスリープを開始
始めた
FromIntegerValueから呼び出された
呼び出されたsetBuildFromClassはスリープ状態になります
スレッドA:クラスJava.lang.String
B目が覚めた!
スレッドB:クラスJava.lang.String <-Java.lang.Integerが必要
出力は時々:
始めた
FromIntegerValueから呼び出された
呼び出されたsetBuildFromClassはスリープ状態になります
Bが始めた
BがfromIntegerValue&setBuildFromClassを呼び出してスリープを開始
スレッドA:クラスJava.lang.Integer <-私たちはJava.lang.Stringを期待しています
B目が覚めた!
スレッドB:クラスJava.lang.Integer
この手法は完全にスレッドセーフです。 enum値は、使用される前に、シングルスレッドによって一度だけ初期化されることが保証されています。ただし、それがenumクラスがロードされたときなのか、enum値自体に初めてアクセスされたときなのかはわかりません。列挙型ベースのシングルトンの2番目のコピーを取得するためのリフレクションを使用する方法さえないため、この手法の使用は実際には他の手法よりも少し安全です。
synchronizedを追加すると、列挙型との不整合な状態が回避されます。
以下のコードを実行すると、常に「One」と印刷されてうまくロックされます。ただし、コメントアウトすると同期他の値も出力されます。
import Java.util.Random;
import Java.util.concurrent.atomic.AtomicInteger;
public class TestEnum
{
public static AtomicInteger count = new AtomicInteger(1);
public static enum E
{
One("One"),
Two("Two");
String s;
E(final String s)
{
this.s = s;
}
public void set(final String s)
{
this.s = s;
}
public String get()
{
return this.s;
}
}
public static void main(final String[] args)
{
doit().start();
doit().start();
doit().start();
}
static Thread doit()
{
return new Thread()
{
@Override
public void run()
{
String name = "MyThread_" + count.getAndIncrement();
System.out.println(name + " started");
try
{
int i = 100;
while (--i >= 0)
{
synchronized (E.One)
{
System.out.println(E.One.get());
E.One.set("A");
Thread.sleep(new Random().nextInt(100));
E.One.set("B");
Thread.sleep(new Random().nextInt(100));
E.One.set("C");
Thread.sleep(new Random().nextInt(100));
E.One.set("One");
System.out.println(E.One.get());
}
}
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name + " ended");
}
};
}
}