一、理解Looper:消息循环

Android消息循环是针对线程的(每个线程都可以有自己的消息队列和消息循环)。

Looper用于封装了android线程中的消息循环,默认情况下一个线程是不存在消息循环(message loop)的,需要调用Looper.prepare()来给线程创建一个消息循环,调用Looper.loop()来使消息循环起作用,从消息队列里取消息,处理消息。

Activity的MainUI线程默认是有消息队列的。所以在Activity中新建Handler时,不需要先调用Looper.prepare()。

注:写在Looper.loop()之后的代码不会被立即执行,当调用后 mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。Looper对象通过MessageQueue 来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。一个线程可以有多个Handler,但是只能有一个Looper!

Android系统中Looper负责管理线程的消息队列和消息循环。
可以通过Loop.myLooper()得到当前线程的Looper对象,
通过Loop.getMainLooper()可以获得当前进程的主线程的Looper对象。

例如常见刷新界面:

private void invalidateView() {
        //Looper.getMainLooper返回进程的主线程(UI线程)中Looper对象
        //Looper.myLooper返回当前线程的Looper对象
        if (Looper.getMainLooper() == Looper.myLooper()) {//如果当前线程是主线程
            invalidate();
        } else {
            postInvalidate();
        }
    }

Looper 的源码:

public class Looper {
    // 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象
    private static final ThreadLocal sThreadLocal = new ThreadLocal();
    // Looper内的消息队列
    final MessageQueue mQueue;
    // 当前线程
    Thread mThread;
    // 。。。其他属性

    // 每个Looper对象中有它的消息队列,和它所属的线程
    private Looper() {
        mQueue = new MessageQueue();//每个looper有自己的MessageQueue
        mRun = true;
        mThread = Thread.currentThread();//每个looper有自己的Thread
    }

    // 我们调用该方法会在调用线程的TLS中创建Looper对象
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            // 试图在有Looper的线程中再次创建Looper将抛出异常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());//将looper对象设置为ThreadLocalMap的value值
    }
    // 其他方法
}

二、理解ThreadLocal:线程局部变量

在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal就比较好的解决了这个问题。

应用场景:当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。

当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。

当我们调用get方法的时候,其实每个当前线程中都有一个ThreadLocal。每次获取或者设置都是对该ThreadLocal进行的操作,是与其他线程分开的。

从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。如果允许的话,我们自己就能实现一个这样的功能,只不过恰好JDK就已经帮我们做了这个事情。

1、每个线程thread中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。

2、将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

ThreadLocal本质是操作线程中ThreadLocalMap来实现本地线程变量的存储的

ThreadLocalMap是采用【数组】的方式来存储数据,其中【key(弱引用)】指向当前ThreadLocal对象,value为设的值

ThreadLocal为内存泄漏采取了处理措施,在调用ThreadLocal的get(),set(),remove()方法的时候都会清除线程ThreadLocalMap里所有key为null的Entry

在使用ThreadLocal的时候,我们仍然需要注意,避免使用static的ThreadLocal,分配使用了ThreadLocal后,一定要根据当前线程的生命周期来判断是否需要手动的去清理ThreadLocalMap中清key==null的Entry。

Thread 部分源码:

public class Thread implements Runnable {

    //ThreadLocalMap 
    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    //ThreadLocalMap 
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
}

ThreadLocal部分源码:

ThreadLocalMap 为ThreadLocal的内部类。

/**
 * 线程局部变量。
 *
 * 这些变量与正常变量不同。因为每一个访问线程的变量(通过get或set方法)都有自己线程的变量,独立初始化变量的副本。
 * ThreadLocal实例通常是私有的静态字段在希望关联状态的线程的类中(例如,一个用户ID或交易ID)。
 *
 * 例如:下面的类产生本地唯一的标识符在每个线程中。
 * 一个线程的id在第一次调用 ThreadId.get() 时就分配,在后续调用保持不变。
 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 * // Atomic integer containing the next thread ID to be assigned
 * private static final AtomicInteger nextId = new AtomicInteger(0);
 * <p>
 * // Thread local variable containing each thread's ID
 * private static final ThreadLocal<Integer> threadId =
 * new ThreadLocal<Integer>() {
 * @Override protected Integer initialValue() {
 * return nextId.getAndIncrement();
 * }
 * };
 * <p>
 * // Returns the current thread's unique ID, assigning it if necessary
 * public static int get() {
 * return threadId.get();
 * }
 * }
 * <p>
 * 每个线程只要在线程存活且ThreadLocal实例可访问,都持有线程本地变量的隐式引用
 * <p>
 * 在线程消失后,线程本地实例的所有副本都将受到垃圾回收(除非对这些副本的其他引用存在)。
 */
public class ThreadLocal<T> {

    /**
     * 创建线程本地变量
     */
    public ThreadLocal() {
    }

    /**
     * 返回当前线程的局部线程变量的值。
     * <p>
     * 如果当前线程变量没有值,它首先初始化为调用初始化值方法返回的值。
     *
     * @return 返回本地线程当前线程的值
     */
    public T get() {
        Thread t = Thread.currentThread();//当前线程
        ThreadLocalMap map = getMap(t);//当前线程的map
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//当前线程的map的key为ThreadLocal
            if (e != null)
                return (T) e.value;
        }
        return setInitialValue();
    }

    /**
     * 将当前线程的本地变量的副本设置为指定的值。
     * <p>
     * 大多数子类将不需要重写此方法,只依赖于方法设置线程本地值。
     *
     * @param value 要存储在当前线程本地线程副本中的值。
     */
    public void set(T value) {
        Thread t = Thread.currentThread();//当前线程
        ThreadLocalMap map = getMap(t);//当前线程的map
        if (map != null)
            map.set(this, value);//当前线程的map的key为ThreadLocal
        else
            createMap(t, value);
    }

    /**
     * 为该线程局部变量移除当前线程的值。
     * <p>
     * 如果此线程局部变量随后由当前线程执行,它的值将通过调用其方法被重新初始化
     * <p>
     * 除非其值由临时线程中的当前线程决定。这可能会导致当前线程在多个调用初值的方法。
     *
     * @since 1.5
     */
    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
            m.remove(this);
    }

    /**
     * 获取一个ThreadLocal相关的map。在继承ThreadLocal中重写。
     *
     * @param t 当前线程
     * @return 获取的map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     *
     * 创建一个ThreadLocal相关的map。在继承ThreadLocal中重写。
     *
     * @param t          当前线程
     * @param firstValue 初始化值
     * @param map        存储的map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    /**
     *
     * threadlocalmap是一个定制的哈希映射只适合维护线程局部变量的值。
     *
     *没有操作出口在ThreadLocal类之外。
     * 类是包私有的,允许在类线程中声明字段。
     * 为了帮助处理非常大且寿命长的用法,哈希表条目使用使用弱引用键。
     * 但是,由于不使用引用队列,所以只有在表开始耗尽空间时才保证删除过期条目。
     */
    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal> {
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    ThreadLocal key = e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
        
    
    }
}

threadlocal的示例:

使用ThreadLocal:

public class Bank {

    private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {//重写修改初始值
            return 100;
        }
    };

    public int get() {
        return threadLocal.get();
    }

    public void set() {
        threadLocal.set(threadLocal.get() + 10);
    }
}

定义runnable:

public class MyRunnable implements Runnable {

    private Bank bank;

    public MyRunnable(Bank bank) {
        this.bank = bank;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            bank.set();
            //输出不同线程的threadlocal的值
            System.out.println("Runnable  " + Thread.currentThread() + " : " + bank.get());
        }
    }
}

测试多线程任务:

public class TestBank {

    public static void main(String[] args) throws InterruptedException {
        Bank bank = new Bank();

        //两条线程同时操作一个变量,但是不同线程的结果是互不影响的
        MyRunnable runnable = new MyRunnable(bank);
        Thread thread1 = new Thread(runnable);
        thread1.start();
        Thread thread2 = new Thread(runnable);
        thread2.start();
        thread1.join();
        thread2.join();

        //需要注意的是,这个是主线程中的变量,输出的是ThreadLocal<Integer>的初始值也就是100
        System.out.println("main  " + Thread.currentThread() + " : " + bank.get());
    }
}

测试结果:

Runnable  Thread[Thread-0,5,main] : 110
Runnable  Thread[Thread-0,5,main] : 120
Runnable  Thread[Thread-0,5,main] : 130
Runnable  Thread[Thread-0,5,main] : 140
Runnable  Thread[Thread-0,5,main] : 150
Runnable  Thread[Thread-0,5,main] : 160
Runnable  Thread[Thread-0,5,main] : 170
Runnable  Thread[Thread-0,5,main] : 180
Runnable  Thread[Thread-1,5,main] : 110
Runnable  Thread[Thread-0,5,main] : 190
Runnable  Thread[Thread-1,5,main] : 120
Runnable  Thread[Thread-1,5,main] : 130
Runnable  Thread[Thread-0,5,main] : 200
Runnable  Thread[Thread-1,5,main] : 140
Runnable  Thread[Thread-1,5,main] : 150
Runnable  Thread[Thread-1,5,main] : 160
Runnable  Thread[Thread-1,5,main] : 170
Runnable  Thread[Thread-1,5,main] : 180
Runnable  Thread[Thread-1,5,main] : 190
Runnable  Thread[Thread-1,5,main] : 200
main  Thread[main,5,main] : 100

可见:

ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。

它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题。

ThreadLocal 与 Synchronized区别

相同:ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

不同:Synchronized同步机制采用了“以时间换空间”的方式,仅提供一份变量,让不同的线程排队访问;而ThreadLocal采用了“以空间换时间”的方式,每一个线程都提供了一份变量,因此可以同时访问而互不影响。

以时间换空间->即加锁方式,某个区域代码或变量只有一份,节省了内存,但是会形成很多线程等待现象,因此浪费了时间而节省了空间。

以空间换时间->为每一个线程提供一份变量,多开销一些内存,但是呢线程不用等待,可以一起执行而相互之间没有影响。

小结:
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。

在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

注意:

volatile 保证了线程总是看到最新的值,threadlocal 是一个线程一份
ThreadLocal存放的变量是该线程里的局部变量,synchronized操作的是各个线程的共享变量。

ThreadLocal 就是把变量分成很多个拷贝,每个线程拥有一个。
这里没有所谓的最后的结果,每个线程单独操作自己的变量,和其他的变量没关系。
你就理解成都是各干各的,如果说真要用到跟最终结果有关系,还是老老实实用synchronized

synchronized和volatile的区别:

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是
立即可见的。

2)禁止进行指令重排序。
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

1.volatile仅能使用在变量级别;
synchronized则可以使用在变量、方法、和类级别的

2.volatile仅能实现变量的修改可见性,并不能保证原子性;
synchronized则可以保证变量的修改可见性和原子性

3.volatile不会造成线程的阻塞;
synchronized可能会造成线程的阻塞。

4.volatile标记的变量不会被编译器优化;
synchronized标记的变量可以被编译器优化