在 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 里面及时清除。比加锁要高效。