K:枚舉的線程安全性及其序列化問題

来源:http://www.cnblogs.com/MyStringIsNotNull/archive/2017/12/10/8018243.html
-Advertisement-
Play Games

  枚舉是如何保證線程安全的且其在序列化和反序列化的操作中是單例的?   要想看源碼,首先得有一個類吧,那麼枚舉類型到底是什麼類呢?是enum嗎?答案很明顯不是,enum就和class一樣,只是一個關鍵字,他並不是一個類,那麼枚舉是由什麼類維護的呢,我們簡單的 ...


  枚舉是如何保證線程安全的且其在序列化和反序列化的操作中是單例的?

  要想看源碼,首先得有一個類吧,那麼枚舉類型到底是什麼類呢?是enum嗎?答案很明顯不是,enum就和class一樣,只是一個關鍵字,他並不是一個類,那麼枚舉是由什麼類維護的呢,我們簡單的寫一個枚舉:

public enum T {
    SPRING,SUMMER,AUTUMN,WINTER;
}

然後我們使用反編譯,看看這段代碼到底是怎麼實現的,反編譯(Java的反編譯)後代碼內容如下:

public final class T extends Enum
{
    private T(String s, int i)
    {
        super(s, i);
    }
    public static T[] values()
    {
        return (T[])$VALUES.clone();
    }
 
    public static T valueOf(String s)
    {
        return (T)Enum.valueOf(T, s);
    }
 
    public static final T SPRING;
    public static final T SUMMER;
    public static final T AUTUMN;
    public static final T WINTER;
    private static final T $VALUES[];
    static
    {
        SPRING = new T("SPRING", 0);
        SUMMER = new T("SUMMER", 1);
        AUTUMN = new T("AUTUMN", 2);
        WINTER = new T("WINTER", 3);
        $VALUES = (new T[] {
            SPRING, SUMMER, AUTUMN, WINTER
        });
    }
}

通過反編譯後代碼我們可以看到,public final class T extends Enum,說明,該類是繼承了Enum類的,同時final關鍵字告訴我們,這個類也是不能被繼承的。當我們使用enmu來定義一個枚舉類型的時候,編譯器會自動幫我們創建一個final類型的類繼承Enum類,所以枚舉類型不能被繼承,我們看到這個類中有幾個屬性和方法。
我們可以看到:

public static final T SPRING;
public static final T SUMMER;
public static final T AUTUMN;
public static final T WINTER;
private static final T $VALUES[];
static
{
    SPRING = new T("SPRING", 0);
    SUMMER = new T("SUMMER", 1);
    AUTUMN = new T("AUTUMN", 2);
    WINTER = new T("WINTER", 3);
    $VALUES = (new T[] {
        SPRING, SUMMER, AUTUMN, WINTER
    });
}

都是static類型的,因為static類型的屬性會在類被載入之後被初始化,當一個Java類第一次被真正使用到的時候靜態資源被初始化、Java類的載入和初始化過程都是線程安全的。所以,創建一個enum類型是線程安全的。

為什麼用枚舉實現的單例是最好的方式

在單例模式的七種寫法中,我們看到一共有七種實現單例的方式,其中,Effective Java作者Josh Bloch提倡使用枚舉的方式,既然大神說這種方式好,那我們就要知道它為什麼好?

1. 枚舉寫法簡單

  寫法簡單這個大家看看單例模式的七種寫法裡面的實現就知道區別了。

public enum EasySingleton{
    INSTANCE;
}

你可以通過EasySingleton.INSTANCE來訪問。

2. 枚舉自己處理序列化

  我們知道,以前的所有的單例模式都有一個比較大的問題,就是一旦實現了Serializable介面之後,就不再是單例得了,因為,每次調用readObject()方法返回的都是一個新創建出來的對象,有一種解決辦法就是使用readResolve()方法來避免此事發生。但是,為了保證枚舉類型像Java規範中所說的那樣,每一個枚舉類型極其定義的枚舉變數在JVM中都是唯一的,在枚舉類型的序列化和反序列化上,Java做了特殊的規定。原文如下:

  Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant’s name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant’s enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored–all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.

翻譯如下(限於本人的英文水平,翻譯不好的地方請見諒):
枚舉常數的序列化方式不同於普通Serializable或者Externalizable對象的序列化(相關博文:K:java中序列化的兩種方式—Serializable或Externalizable)。枚舉常數的序列化表單僅由其name屬性構成;常數的欄位值不存在於表單中。在序列化一個枚舉常量時,ObjectOutputStream對象會寫入由枚舉常量的name方法所返回的值(一般也就是name屬性的值,也就是枚舉常量的名稱)。在反序列化枚舉常量時,通過ObjectInputStream對象從相關的流中讀取枚舉常量的name屬性值(也就是枚舉常量的名稱),然後通過調用java.lang.Enum對象的valueOf方法,將從流中獲取的枚舉常量的枚舉類型(指的是繼承了Enum類的相關的子類)及其常量名稱(也就是枚舉常量的名稱)一起作為該方法的參數傳遞給該valueOf方法。像其它Serializable或者Externalizable對象一樣,枚舉常量可以作為隨後出現的序列化流的反向引用。枚舉常量被序列化的過程無法自定義:在序列化和反序列化期間將忽略由枚舉類型定義的任何特定於該類的writeObject,readObject,readObjectNoData,writeReplace和readResolve方法。類似地,任何serialPersistentFields或serialVersionUID欄位聲明也被將被忽略。所有的枚舉類型的fixedserialVersionUID值都是0L。記錄枚舉類型的可序列化欄位其相關的數據是不必要的,因為發送的數據類型並沒有發生變化。
以下代碼演示說明枚舉常量的序列化只保存了其枚舉常量的name屬性值

示例代碼:

package other.serial;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * 該類用於測試枚舉類的序列化是不是只是序列化了name屬性的值,也就是枚舉常量的常量名
 * 
 * @author 學徒
 * 
 */
public enum AS implements Serializable
{
    H("sss");
    private String names;

    private AS(String names)
    {
        this.names = names;
    }

    public void setNames(String names)
    {
        this.names = names;
    }

    public String getNames()
    {
        return this.names;
    }
}

class AB
{
    public static void main(String args[]) throws Exception
    {
        // 設置枚舉對象的names屬性值為as
        AS.H.setNames("as");
        System.out.println("原始枚舉對象的屬性值為" + AS.H.getNames());
        // 對枚舉對象進行序列化,如果其還序列化了相關的域值,則其應該保留了names屬性域的值為as
        new ObjectOutputStream(new FileOutputStream("H:\\as.txt"))
                .writeObject(AS.H);
        System.out.println("序列化成功");
        // 修改枚舉對象中的names屬性域的屬性值
        AS.H.setNames("SS");
        // 對已序列化的對象進行反序列化處理,若其還序列化了枚舉對象的相關的域值,則其反序列化出來的names的屬性值應當為as
        AS a = (AS) new ObjectInputStream(new FileInputStream("H:\\as.txt"))
                .readObject();
        System.out.println("反序列化成功");
        // 通過輸出結果判斷
        System.out.println("反序列化的對象的值" + a.getNames());
        System.out.println(a == AS.H);
    }
}


輸出結果:
原始枚舉對象的屬性值為as
序列化成功
反序列化成功
反序列化的對象的值SS
true

  通過分析其相應的輸出結果可以看出枚舉類型對象在序列化的時候,枚舉常數的序列化僅序列化了其name屬性值,也就是枚舉常量名稱

  概括起來就是說,在序列化的時候Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是通過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。同時,編譯器是不允許任何對這種序列化機制的定製的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

我們看一下這個valueOf方法:

public static <T extends Enum<T>>T valueOf(Class<T> enumType,String name) 
{  
    T result = enumType.enumConstantDirectory().get(name);  
    if (result != null)  
        return result;  
    if (name == null)  
        throw new NullPointerException("Name is null");  
    throw new IllegalArgumentException(  "No enum const " + enumType +"." + name);  
}

  從代碼中可以看到,代碼會嘗試從調用enumType這個Class對象的enumConstantDirectory()方法返回的map中獲取名字為name的枚舉對象,如果不存在就會拋出異常。再進一步跟到enumConstantDirectory()方法,就會發現到最後會以反射的方式調用enumType這個類型的values()靜態方法,也就是上面我們看到的編譯器為我們創建的那個方法,然後用返回結果填充enumType這個Class對象中的enumConstantDirectory屬性。所以,JVM對序列化有保證。

回到目錄|·(工)·)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 基於資料庫的自動化生成工具,根據資料庫表結構自動生成JavaBean、自動生成MyBaits的Mapper映射配置文件、自動生成資料庫設計文檔、自動生成各種SQL等。支持MySQL、Oracle、SQLServce、PostgreSQL四種資料庫,支持Window、Linux、MacBook等多個... ...
  • 1、作用域public,private,protected,以及不寫時的區別 答: 區別如下: 2、Anonymous Inner Class (匿名內部類) 是否可以extends(繼承)其它類,是否可以implements(實現)interface(介面) 答: 匿名的內部類是沒有名字的內部類。 ...
  • 1. 列表list 1.1 切片# 定義一個list。list = [1, 2, 3, 4, 5] 從左往右讀取字元(預設步長為 1 )。如:list[-2:-1] # 返回一個list數據類型,[6]list[2] # 返回一個int數據類型, 3 從右往左讀取字元串(預設步長為 1 )。如:li ...
  • 在開發項目時,有時候需要用到上傳功能,比如頭像上傳等,其文件會保存到伺服器中。但是我發現在用eclipse做項目的過程中,每次重新部署項目,原來上傳的文件就會丟失。 其原因是因為每次項目修改後,eclipse會把我們放在工作空間workspace中的這個項目拷貝到伺服器下(如tomcat的webap ...
  • ...
  • 在學習有關java枚舉的時候,我們知道了所有的枚舉類型均是繼承自java.lang.Enum類的,且所有的枚舉常量均是該枚舉類型的一個對象,且對象名即為該枚舉常量的名稱。例子如下:源碼: 反編譯後的代碼: 在寫代碼的時候,由於輸入法切換的問題,發現枚舉常量的常量名稱居然是可以使用中文的。代碼如下: ...
  • 一、PTA實驗作業 題目1:查驗身份證 1. 本題PTA提交列表 2. 設計思路 3.代碼截圖 4.本題調試過程碰到問題及PTA提交列表情況說明。 部分正確 :將x改為大寫x 題目2:藏頭詩 1. 本題PTA提交列表 2. 設計思路 3.代碼截圖 4.本題調試過程碰到問題及PTA提交列表情況說明。 ...
  • 使用會話維持狀態 一、會話 為了實現關聯同一個用戶端的多個請求和這些請求之間數據的共用,需要用到會話,會話用於維持請求和請求之間的狀態。從伺服器的角度,當用戶的Web瀏覽器打開第一個鏈接到伺服器的套接字時請求就開始了,直到伺服器返回最後一個數據包並關閉鏈接是,該請求將結束。此時用戶瀏覽器和伺服器之間 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...