2010-03-28 21 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"); 
      } 
     }; 
    } 
}