代理模式 &ThreadLocal 在递归调用中的一种应用

在 Gson 里面看到一个代码,感觉写的挺有意思的。
源码在 Gson.java 里面,用代理模式,解决了递归调用的问题,并且用一个 ThreadLocal 变量,避免了多线程访问的问题。

  /**
   * This thread local guards against reentrant calls to getAdapter(). In
   * certain object graphs, creating an adapter for a type may recursively
   * require an adapter for the same type! Without intervention, the recursive
   * lookup would stack overflow. We cheat by returning a proxy type adapter.
   * The proxy is wired up once the initial adapter has been created.
   */
  private final ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>> calls
      = new ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>>();
  public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
    TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
    if (cached != null) {
      return (TypeAdapter<T>) cached;
    }

    Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get();
    boolean requiresThreadLocalCleanup = false;
    if (threadCalls == null) {
      threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>();
      calls.set(threadCalls);
      requiresThreadLocalCleanup = true;
    }

    // the key and value type parameters always agree
    FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);
    if (ongoingCall != null) {
      return ongoingCall;
    }

    try {
      FutureTypeAdapter<T> call = new FutureTypeAdapter<T>();
      threadCalls.put(type, call);

      for (TypeAdapterFactory factory : factories) {
        TypeAdapter<T> candidate = factory.create(this, type);
        if (candidate != null) {
          call.setDelegate(candidate);
          typeTokenCache.put(type, candidate);
          return candidate;
        }
      }
      throw new IllegalArgumentException("GSON cannot handle " + type);
    } finally {
      threadCalls.remove(type);

      if (requiresThreadLocalCleanup) {
        calls.remove();
      }
    }
  }

初看的时候,没有明白 threadCalls 的作用。在 try 模块中,先是 threadCalls.put(type, call);,然后就在 finally 里面 threadCalls.remove(type);,好像没有什么意义。还特意用 ThreadLocal 存储了一下 calls.set(threadCalls);

1. 代理模式的作用

先说说递归调用发生的地方,是在TypeAdapter<T> candidate = factory.create(this, type);,这个 create() 方法里面,也有可能会调用 getAdapter() ,没有适当处理的话,就可能会无限循环下去。
所以这里定义了一个代理对象

FutureTypeAdapter<T> call = new FutureTypeAdapter<T>();
threadCalls.put(type, call);

当递归调用到这里的时候,就会先返回代理对象。

    FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);
    if (ongoingCall != null) {
      return ongoingCall;
    }

而当真正找到正确的 TypeAdapter<T> candidate = factory.create(this, type);,会调用call.setDelegate(candidate);。可以看 FutureTypeAdapter 的实现,就是简单做一下代理。

2. ThreadLocal 的作用

getAdapter() 是 Gson 类的方法,理论上存在多线程的可能。
处理多线程,通常是加锁,但是这里用 ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>> calls存储threadCalls,然后在 finally 里面及时清除。比加锁要高效。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。