簡體   English   中英

我的Java緩存線程安全且性能良好嗎?

[英]Is my Java cache thread-safe and performant?

我實現了一個小幫助程序類,該類提供了枚舉的valueOf方法的簡單故障保護實現。 這意味着,如果找不到該值,它將返回null而不是異常。

這是代碼:

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;

import java.io.Serializable;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * <p>
 * This permits to easily implement a failsafe implementation of the enums's valueOf method
 * </p>
 *
 * <p>
 * Basic usage exemple on an enum class called MyEnum:
 * FailSafeValueOf.get(MyEnum.class).valueOf("EnumName");
 * </p>
 *
 * @author Sebastien Lorber <i>(lorber.sebastien@gmail.com)</i>
 */
public class FailSafeValueOf<T extends Enum<T>> implements Serializable {

    /**
     * This will cache a FailSafeValueOf for each enum so that we do not need to recompute a map each time
     */
    private static final Map< Class<? extends Enum<?>> , FailSafeValueOf<? extends Enum<?>> >  CACHE = Maps.newHashMap();


    private final Map<String,T> nameToEnumMap;
    private FailSafeValueOf(Class<T> enumClass) {
        Map<String,T> map = Maps.newHashMap();
        for ( T value : EnumSet.allOf(enumClass)) {
            map.put( value.name() , value);
        }
        this.nameToEnumMap = ImmutableMap.copyOf(map);
    }

    /**
     * Returns the value of the given enum element
     * If the element is not found, null will be returned, and no exception will be thrown
     * @param enumName
     * @return
     */
    public T valueOf(String enumName) {
        return nameToEnumMap.get(enumName);
    }


    /**
     * Get a failsafe value of implementation for a given enum
     * @param enumClass
     * @param <U>
     * @return
     */
    public static <U extends Enum<U>> FailSafeValueOf<U> get(Class<U> enumClass) {
        FailSafeValueOf<U> fsvo = (FailSafeValueOf<U>)CACHE.get(enumClass);
        if ( fsvo == null ) {
            synchronized (FailSafeValueOf.class) {
                fsvo = (FailSafeValueOf<U>)CACHE.get(enumClass);
                if ( fsvo == null ) {
                    fsvo = new FailSafeValueOf<U>(enumClass);
                    CACHE.put(enumClass,fsvo);
                }
            }
        }
        return fsvo;
    }

}

因為我不想每次訪問都創建一個新的FailSafeValueOf的(很少)開銷,所以我做了一個緩存,為每個已訪問的枚舉保留一個已經建立的FailSafeValueOf實例。

我不習慣處理並發。 在FailSafeValueOf是不可變的並且對於同一枚舉的get方法可以返回2個不同的FailSafeValueOf實例的情況下,並發訪問可能不是一個大問題。 但是我想知道我的線程安全性實現方式是否可行,以及它是否真的是線程安全性的? (主要用於學習目的)

我不想使我的方法同步,因為過了一段時間,所有FailSafeValueOf都在緩存中創建,並且不需要禁止並發線程進入get方法。

因此,我要做的是先檢查是否存在緩存未命中,然后創建一個將自動進行同步的塊:再次檢查緩存並最終創建實例。 它是線程安全的,並且是滿足這種需求的方法嗎?

順便說一下,枚舉通常具有少量的值。 在這種情況下,HashMap是否合適? 遍歷EnumSet並獲取適當的值而不是使用緩存是否更快?


編輯:

請注意,我的課程不是那么有用,因為Guava團隊使用了Enums.getIfPresent()方法,該方法返回Optional

您的類不是線程安全的,因為您沒有在CACHE.get()方法周圍進行同步。 假定Maps.newHashMap()返回HashMap而不是ConcurrentHashMap類。 您可以在以下代碼片段中看到此內容:

    // you need to move your synchronized block out to here
    FailSafeValueOf<U> fsvo = (FailSafeValueOf<U>)CACHE.get(enumClass);
    if ( fsvo == null ) {
        synchronized (FailSafeValueOf.class) {
           ...
           CACHE.put(enumClass,fsvo);
        }
    }

如果經常調用該方法,則需要在CACHE.get(...)周圍移動synchronized ,或者切換到使用ConcurrentHashMap 問題在於,當當前線程正在從HashMap讀取HashMap ,該線程將被另一個線程更新-由於競爭條件,這很容易引起問題。

盡管稍有不同,但您還應該查看“雙重檢查鎖定”類文檔,以了解更多有關嘗試像這樣從同步中拯救自己的困難。

最后,除非您確實需要該鎖粒度,否則我將在CACHE對象而不是不推薦的類上進行同步。

這是有問題的:

synchronized (FailSafeValueOf.class)

您只應在私人成員上進行同步。 類對象是可公開訪問的,其他代碼也可能選擇對其進行鎖定,從而導致潛在的死鎖。

另外,允許並發獲取的正確解決方案是讀寫器鎖,而不是跳過該鎖。 如果您當前的代碼是在編寫時讀取的,則可能會遇到各種各樣的麻煩。

我會用

private static final Cache<Class<?>, FailSafeValueOf<?>> CACHE
    = CacheBuilder.newBuilder().build(
        new CacheLoader<Class<?>, FailSafeValueOf<?>>() {
            @Override
            public FailSafeValueOf<?> load(Class<?> key) throws Exception {
                return new FailSafeValueOf(key);
            }
    });

在最少的工作量下為您提供最大的靈活性 此外,您可以肯定它可以正確且快速地運行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM