简体   繁体   English

我的Java缓存线程安全且性能良好吗?

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

I've implemented a little helper class that provides an easy fail-safe implementation of the enum's valueOf method. 我实现了一个小帮助程序类,该类提供了枚举的valueOf方法的简单故障保护实现。 This means in case the value is not found, it returns null instead of an exception. 这意味着,如果找不到该值,它将返回null而不是异常。

Here's the code: 这是代码:

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

}

Because i don't want the (little) overhead of creating a new FailSafeValueOf at each access, i've made a cache that keeps for each enum already accessed an already built FailSafeValueOf instance. 因为我不想每次访问都创建一个新的FailSafeValueOf的(很少)开销,所以我做了一个缓存,为每个已访问的枚举保留一个已经建立的FailSafeValueOf实例。

I'm not used to handle concurrency. 我不习惯处理并发。 A concurrent access may not be a big problem in such a case as FailSafeValueOf is immutable and 2 different instances of FailSafeValueOf can be returned by the get method for a same enum. 在FailSafeValueOf是不可变的并且对于同一枚举的get方法可以返回2个不同的FailSafeValueOf实例的情况下,并发访问可能不是一个大问题。 But i'd like to know if my implementation of thread-safety is the way to do, and if it's really thread-safe? 但是我想知道我的线程安全性实现方式是否可行,以及它是否真的是线程安全性的? (mostly for learning purpose) (主要用于学习目的)

I don't want to make my method synchronized because after some time, all FailSafeValueOf are created in the cache, and there is no need to forbid concurrent threads to enter the get method. 我不想使我的方法同步,因为过了一段时间,所有FailSafeValueOf都在缓存中创建,并且不需要禁止并发线程进入get方法。

So what i've made is to check first if there's a cache miss, and then create a synchronized block that will atomically: check again the cache and eventually create the instance. 因此,我要做的是先检查是否存在缓存未命中,然后创建一个将自动进行同步的块:再次检查缓存并最终创建实例。 Is it thread-safe and the way to do for such a need? 它是线程安全的,并且是满足这种需求的方法吗?

By the way, enums often have a small number of values. 顺便说一下,枚举通常具有少量的值。 In such a case, is the HashMap an appropriate structure? 在这种情况下,HashMap是否合适? Is it faster to iterate over the EnumSet and get the appropriate value, instead of using a cache? 遍历EnumSet并获取适当的值而不是使用缓存是否更快?


Edit: 编辑:

Please notice that my class is not so useful because the Guava team has releated a method Enums.getIfPresent() which returns an Optional 请注意,我的课程不是那么有用,因为Guava团队使用了Enums.getIfPresent()方法,该方法返回Optional

Your class is not thread-safe because you are not synchronizing around the CACHE.get() method. 您的类不是线程安全的,因为您没有在CACHE.get()方法周围进行同步。 This assumes that Maps.newHashMap() returns a HashMap and not a ConcurrentHashMap class. 假定Maps.newHashMap()返回HashMap而不是ConcurrentHashMap类。 You can see this in this code snippet: 您可以在以下代码片段中看到此内容:

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

You would need to move the synchronized around the CACHE.get(...) or switch to using a ConcurrentHashMap if that method is called frequently. 如果经常调用该方法,则需要在CACHE.get(...)周围移动synchronized ,或者切换到使用ConcurrentHashMap The problem is that the HashMap would be updated by another thread while the current thread is reading from it -- this can easily cause problems because of race conditions. 问题在于,当当前线程正在从HashMap读取HashMap ,该线程将被另一个线程更新-由于竞争条件,这很容易引起问题。

Although slightly different, you should also look into the class "Double Check Locking" documentation to understand more the difficulties about trying to save yourself from synchronization like this. 尽管稍有不同,但您还应该查看“双重检查锁定”类文档,以了解更多有关尝试像这样从同步中拯救自己的困难。

Lastly, I would synchronize on the CACHE object instead of the class which is not recommended unless you really need to have that lock granularity. 最后,除非您确实需要该锁粒度,否则我将在CACHE对象而不是不推荐的类上进行同步。

This is problematic: 这是有问题的:

synchronized (FailSafeValueOf.class)

You should only synchronize on private members. 您只应在私人成员上进行同步。 The class object is publicly accessible, and other code might choose to lock on it as well, leading to potential deadlock. 类对象是可公开访问的,其他代码也可能选择对其进行锁定,从而导致潜在的死锁。

Also, the correct solution to allow concurrent get is a reader-writer lock, not skipping the lock. 另外,允许并发获取的正确解决方案是读写器锁,而不是跳过该锁。 If your current code reads while writing you could get all kinds of grief. 如果您当前的代码是在编写时读取的,则可能会遇到各种各样的麻烦。

I'd use 我会用

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

which gives you the most flexibility for the least work. 在最少的工作量下为您提供最大的灵活性 Moreover, you can be pretty sure that it works correctly and fast. 此外,您可以肯定它可以正确且快速地运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM