Android线程管理之ThreadLocal理解及应用场景

前言:

     最近在学习总结Android的动画效果,当学到Android属性动画的时候大体看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣!查阅了一下资料发现Android最重要的Handler消息机制里面的Looper存储也是采用ThreadLocal,开源框架EventBus存储当前线程下的发送事件队列状态也是采用ThreadLocal,那么为什么要使用ThreadLocal呢?ThreadLocal是什么呢?它能解决什么样的问题呢?带着这么疑问来学习下ThreadLocal。html

     线程管理相关文章地址:java

ThreadLocal介绍

   ThreadLocal若是单纯从字面上理解的话好像是“本地线程”的意思,其实并非这个意思,只是这个名字起的太容易让人误解了,它的真正的意思是线程本地变量。看看官方怎么说的。安全

/**
 * Implements a thread-local storage, that is, a variable for which each thread
 * has its own value. All threads share the same {@code ThreadLocal} object,
 * but each sees a different value when accessing it, and changes made by one
 * thread do not affect the other threads. The implementation supports
 * {@code null} values.
 *
 * @see java.lang.Thread
 * @author Bob Lee
 */

   哈哈做为学渣英语很差,借助百度翻译了一下。翻译以下也不知道对不对多线程

   实现一个线程本地的存储,也就是说,每一个线程都有本身的局部变量。全部线程都共享一个ThreadLocal对象,可是每一个线程在访问这些变量的时候能获得不一样的值,每一个线程能够更改这些变量而且不会影响其余的线程,而且支持null值。并发

ThreadLocal理解

咱们先看下属性动画为每一个线程设置AnimationHeadler的框架

    private static AnimationHandler getOrCreateAnimationHandler() {
        AnimationHandler handler = sAnimationHandler.get();
        if (handler == null) {
            handler = new AnimationHandler();
            sAnimationHandler.set(handler);
        }
        return handler;
    }

由于protected static ThreadLocal<AnimationHandler> sAnimationHandler =new ThreadLocal<AnimationHandler>();这里没有采用初始化值,这里不是经过一个变量的拷贝而是每一个线程经过new建立一个对象出来而后保存。不少人认为ThreadLocal是为了解决共享对象的多线程访问问题的,这是错误的说法,由于不管是经过初始化变量的拷贝仍是直接经过new建立本身局部变量,ThreadLocal.set() 到线程中的对象是该线程本身使用的对象,其余线程是不须要访问的,也访问不到的。各个线程中访问的是不一样的对象,改变的也是本身独立的对象,自己就不属于同一个对象,没有共享的概念,更加不多是解决共享对象的多线程访问的。异步

经过上面的理解总结如下几点ide

 1.每一个线程读拥有本身的局部变量函数

     每一个线程都有一个独立于其余线程的上下文来保存这个变量,一个线程的本地变量对其余线程是不可见的oop

 2.独立于变量的初始化副本,或者初始化一个属于本身的变量

     ThreadLocal能够给一个初始值,而每一个线程都会得到这个初始化值的一个副本,这样才能保证不一样的线程都有一份拷贝,一样也能够new的方式为线程建立一个变量

 3.变量改变只与当前线程关联,线程之间互不干扰

    ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每一个线程处理本身的状态而引入的一个机制。

因此ThreadLocal既不是为了解决共享多线程的访问问题,更不是为了解决线程同步问题,ThreadLocal的设计初衷就是为了提供线程内部的局部变量,方便在本线程内随时随地的读取,而且与其余线程隔离。

ThreadLocal使用场景

    说了那么多的概念,归根到底咱们在何时才使用ThreadLocal呢?不少时候咱们会建立一些静态域来保存全局对象,那么这个对象就可能被任意线程访问,若是能保证是线程安全的,那却是没啥问题,可是有时候很难保证线程安全,这时候咱们就须要为每一个线程都建立一个对象的副本,咱们也能够用ConcurrentMap<Thread, Object>来保存这些对象,这样会比较麻烦,好比当一个线程结束的时候咱们如何删除这个线程的对象副本呢?若是使用ThreadLocal就不用有这个担忧了,ThreadLocal保证每一个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的而且 ThreadLocal 实例是可访问的;在线程消失以后,其线程局部实例的全部副本都会被垃圾回收(除非存在对这些副本的其余引用)。经查阅资料大体获得如下两种场景:

1.)当某些数据以线程为做用域,而且不一样线程拥有不一样数据副本的时候。

   ThreadLocal使用场合主要解决多线程中数据因并发产生不一致的问题。ThreadLocal以空间换时间,为每一个线程的中并发访问的数据提供一个副本,经过访问副原本运行业务,这样的结果是耗费了内存,但大大减小了线程同步所带来的线程消耗,也减小了线程并发控制的复杂度。

   例如Android的Handler消息机制,对于Handler来讲,它须要获取当前线程的looper很显然Looper的做用域就是线程而且不一样线程具备不一样的Looper,这个时候经过ThreadLocal就能够轻松实现Looper在线程中的存取。再例如开源框架EventBus,EventBus须要获取当前线程的PostingThreadState对象,不一样的PostingThreadState一样做用于不一样的线程,EventBus能够很轻松的获取当前线程下的PostingThreadState对象,而后进行相关操做。

2.)复杂逻辑下对象传递,好比监听器的传递

   使用参数传递的话:当函数调用栈更深时,设计会很糟糕,为每个线程定义一个静态变量监听器,若是是多线程的话,一个线程就须要定义一个静态变量,没法扩展,这时候使用ThreadLocal就能够解决问题。

 ThreadLocal使用举例

  举一个简单的例子,让每一个线程拥有本身惟一的一个任务队列,相似EventBus的实现。

  private static final ThreadLocal<PriorityQueue<TaskItem>> queueThreadLocal = new ThreadLocal<PriorityQueue<TaskItem>>() {
        @Override
        protected PriorityQueue<TaskItem> initialValue() {
            return new PriorityQueue<>(5);
        }
    };


    public PriorityQueue<TaskItem> getTaskQueue() {
        PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
        return taskItems;
    }


    public void addTask(TaskItem taskItem) {
        PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
        taskItems.add(taskItem);
    }

    public void removeTask(TaskItem taskItem) {
        PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
        if (taskItems.contains(taskItem)) {
            taskItems.remove(taskItem);
        }
    }

    private void exceTask() {
        PriorityQueue<TaskItem> taskItems = queueThreadLocal.get();
        if (!taskItems.isEmpty()) {
            TaskItem taskItem = taskItems.poll();
            taskItem.exceTask();
        }
    }

 附上TaskItme代码:

public class TaskItem implements Comparable {
    private long Id;
    private String name;
    private int priority;

    public long getId() {
        return Id;
    }

    public void setId(long id) {
        Id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPriority() {
        return priority;
    }

    public void setPriority(int priority) {
        this.priority = priority;
    }

    @Override
    public int compareTo(Object arg0) {
        if (TaskItem.class.isInstance(arg0)) {
            TaskItem tm = (TaskItem) arg0;
            if (tm.priority > priority) {
                return -1;
            } else if (tm.priority < priority) {
                return 1;
            }
        }
        return 0;
    }

    public void exceTask() {
        Log.e("exceTask", "exceTask---id:" + Id + " name:" + name);
    }

}

通过上面代码能够看到,你是在哪一个线程提交的任务天然而然的就添加到线程所属的任务队列里面,这里其实经过ConcurrentMap<Thread, Object>保存也是能够的,上面也说了相对比较麻烦。

 

总结:

   因为对ThreadLocal了解不是很深入,仅仅理解那么一点点,也许有些观点不必定正确,但愿看到的朋友批评指正谢谢。若是你们想经过一个例子来学习的话,我的建议看下EventBus中使用ThreadLocal范例,用的很巧妙又很容易让人理解,只是ThreadLocal自己咱们在平常项目开发中使用的比较少,一会半会的很难找到合适的场景来搞懂它。

相关文章
相关标签/搜索