但是,ValueAnimator 其实不仅仅可以用来实现动画,也可以用来实现一些跟帧率相关的业务场景,也就是说,如果不涉及 ui 的话,也是允许在其他子线程中使用 ValueAnimator 的,那么此时,这些工作就不应该影响到主线程的动画,那么它是需要单独另外一份 AnimationHandler 单例对象来管理了。
两者结合下,当有在线程内需要单例模式,而又允许不同线程相互独立运作的场景时,也可以使用 ThreadLocal。
场景4:Choreographer
//Choreographer.sThreadInstance private static final ThreadLocal<Choreographer> sThreadInstance = new ThreadLocal<Choreographer>() { @Override protected Choreographer initialValue() { Looper looper = Looper.myLooper(); if (looper == null) { throw new IllegalStateException("The current thread must have a looper!"); } return new Choreographer(looper); } } //Choreographer#getInstance() public static Choreographer getInstance() { return sThreadInstance.get(); }Choreographer 在 Android 的屏幕刷新机制中扮演着非常重要的角色,想了解的可以看看我之前写的一篇文章:Android 屏幕刷新机制
具体也就不分析了,在这里也列出这个,只是想告诉大伙,在源码中,单例 + ThreadLocal 这种模式蛮常见的,我们有要求线程安全的单例模式,相对应的自然也会有线程内的单例模式,要求不同线程可以互不影响、独立运作的单例场景,如果大伙以后有遇到,不妨尝试就用 ThreadLocal 来实现看看。
其他
源码中,还有很多地方也有用到,View 中也有,ActivityThread 也有,ActivityManagerService 也有,很多很多,但很多地方的应用场景我也还搞不懂,所以也就不列举了。总之,就像主席在《开发艺术探索》中所说的:
一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal
精辟,上述源码中不管是用于缓存功能,还是要求线程独立,还是单例 + ThreadLocal 模式,其实本质上都是上面那句话:某些数据如果是以线程为作用域并且不同线程可以互不影响、独立运作的时候,那么就可以采用 ThreadLocal 了。
《开发艺术探索》中描述的应用场景场景1
一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。
比如对应 Handler 来说,它需要获取当前线程的 Looper,很显然 Looper 的作用域就是线程并且不同线程具有不同的 Looper,这个时候通过 ThreadLocal 就可以轻松实现 Looper 在线程中的存取。如果不采用 ThreadLocal,那么系统就必须提供一个全局的哈希表供 Handler 查找指定线程的 Looper,这样一来就必须提供一个类似于 LooperManager 的类了,但是系统并没有这么做而是选择了 ThreadLocal,这就是 ThreadLocal 的好处
场景2
ThreadLocal 另一个使用场景是复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这个时候可以怎么做呢?
其实这时就可以采用 ThreadLocal,采用 ThreadLocal 可以让监听器作为线程内的全局对象而存在,在线程内部只要通过 get 方法就可以获取到监听器。如果不采用 ThreadLocal,那么我们能想到的可能是如下两种方法:第一种方法是将监听器通过参数的形式在函数调用栈中进行传递,第二种方法就是将监听器作为静态变量供线程访问。上述这两种方法都是有局限性的。第一种方法的问题是当函数调用栈很深的时候,通过函数参数来传递监听器对象这几乎是不可接受的,这会让程序的设计看起来糟糕。第二种方法是可以接受的,但是这种状态是不具有可扩充性的,比如同时有两个线程在执行,那么就需要提供两个静态的监听器对象,如果有 10 个线程在并发执行呢?提供 10 个静态的监听器对象?这显然是不可思议的,而采用 ThreadLocal,每个监听器对象都在自己的线程内部存储,根本就不会有方法 2 的这种问题。
大家好,我是 dasu,欢迎关注我的公众号(dasuAndroidTv),如果你觉得本篇内容有帮助到你,可以转载但记得要关注,要标明原文哦,谢谢支持~