2010-03-28 51 views
26

线程安全的是如何在Java中枚举? 我使用枚举(根据Bloch的Effective Java的)执行Singleton, 我应该担心在所有关于线程安全为我的单身枚举? 有没有办法证明或反驳它是线程安全的?线程安全是如何在java中枚举?

// Enum singleton - the preferred approach 
public enum Elvis { 
    INSTANCE; 
    public void leaveTheBuilding() { ... } 
} 

感谢

+0

你是什么意思的“线程安全”? 'leaveTheBuilding'方法不同步,所以当然可以同时运行多个线程。还是你在说初始化'INSTANCE'? – 2010-03-28 04:20:21

+1

当你说“singleton”时,你的意思是它有可变状态吗?在这种情况下,无论如何你都会失败。 – 2010-03-28 04:46:15

+0

只是我的$ 0.02,但我认为使用枚举来强制Singleton模式是代码混淆。也就是说,我知道布洛赫和其他很多人都认为这一点,我的评论可能是恐龙的咆哮。 – 2010-03-28 06:53:05

回答

8

这项技术绝对是线程安全的。一个枚举值被保证只在一个线程被使用之前初始化一次。但是,我不确定是在何时加载枚举类或第一次访问枚举值本身。使用这种技术实际上比其他技术更安全一些,因为甚至没有反射的方法来获得基于枚举的单例的第二个副本。

+1

如果枚举在其静态前缀(或其他地方)中做了一些非常愚蠢的事情,理论上可能是一个问题。 (静态初始化是与类加载不同的阶段 - 请参阅3参数'Class.forName'。) – 2010-03-28 04:44:47

+0

是的,我试图找到反思的方式来启动另一个枚举,我不能。 – portoalet 2010-03-28 11:48:02

+0

@Mike我怎样才能找出什么时候枚举值被初始化,即当加载枚举类或第一次访问枚举值? – portoalet 2010-03-28 12:12:46

31

由于@Mike是说,枚举的创作是保证线程安全的。但是,添加到枚举类的方法不具有任何线程安全保证。特别地,方法leaveTheBuilding可以同时由多个线程执行。如果此方法有副作用(更改某些变量的状态),那么你需要考虑如何保护它(即,使其​​)或其部分。

9

自定义枚举定义可能不是线程安全的。例如,

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调用它fromIntegerValue &设置BuildFromClass并开始睡觉

一开始

一个名为fromIntegerValue

一个名为setBuildFromClass并开始睡觉

线程A:类java.lang.String

乙醒了!

线程B:类java.lang.String < - 我们期望java.lang。整数

有时输出将是:

一开始

一个名为fromIntegerValue

一个名为setBuildFromClass并开始睡觉

乙开始

b调用它fromIntegerValue & setBuildFromClass并开始睡觉

线程A:类java.lang.Integer < - 我们期待java.lang.String中

乙醒了!

线程B:类java.lang.Integer

1

添加同步避免了与枚举不一致的状态。

下面的代码将运行将很好地锁定打印“一”。但是,当您注释掉同步的时,还会打印其他值。

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"); 
      } 
     }; 
    } 
}