Android开发经验谈程序员Android开发

Java | 使用ThreadLocal实现无锁线程安全

2019-07-29  本文已影响335人  彭旭锐

前言

ThreadLocal 思维导图 线程安全 示意图

1. 用法

ThreadLocal的用法很简单,ThreadLocal提供了下列的public与protected方法:

ThradlLocal UML类图

现在我们查看ThreadLocal中与上述几个方法有关的代码,简化代码如下:

// ThreadLocal.java

// ThreadLocal构造方法里什么都没做
public ThreadLocal() {
    // do nothing
}

// 定义ThreadLocal变量的初始值
protected T initialValue() {
    // 默认的初始值为null
    return null;
}

// 内部方法:用于设置当前线程里ThreadLocal变量初始值  
private T setInitialValue() {
    T value = initialValue();
    // 其实ThreadLocal的源码并不是直接调用set(),但源码中这部分代码
    // 就相当于调用set()方法,这是为了防止子类重写set()造成异常
    set(value);
    return value;
}

// 获取当前线程中ThreadLocal变量的值  
public T get() {
    Thread t = Thread.currentThread();
    // ThreadLocalMap是什么?稍后介绍
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 存在匹配的Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            // 变量的值不为null,返回
            T result = (T)e.value;
            return result;
        }
    }
    // 获取的值为空,设置变量的初始值并返回
    return setInitialValue();
}
  
// 设置当前线程中ThreadLocal变量的值
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        // ThreadLocalMap懒初始化,直到设置值的时候才创建
        createMap(t, value);
}

// 移除当前线程中ThreadLocal变量的值
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

ThreadLocalMap存储在Thread的属性中,简化代码如下:

// Thread.java

ThreadLocal.ThreadLocalMap threadLocals = null;

// 线程退出之前,会置空threadLocals变量,以便随后GC
private void exit() {
    // ...
    threadLocals = null;
    // ...
}

分析代码,可以总结出方法的用法:

总结一下ThreadLocal的生命周期,如下图所示:

ThreadLocal生命周期 示意图

2. 例子

我们看看android.os.Looper.java 中是如何使用ThreadLocal,简化代码如下:

// /frameworks/base/core/java/android/os/Looper.java

public class Looper {
    // ...
    // 静态ThreadLocal变量,所有类实例共享同一个ThreadLocal变量
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 设置ThreadLocal变量的值
        sThreadLocal.set(new Looper(quitAllowed));
    }

    public static Looper myLooper() {
        // 获取ThreadLocal变量的值
        return sThreadLocal.get();
    }

    public static void prepare() {
        prepare(true);
    }
    // ...
}

我们可以画出Looper中访问ThreadLocal的Timethreads图,如下图所示,不同线程独占一个Looper变量,线程间不存在共享资源。可以看到ThreadLocal实现了无锁线程安全,避免了加解锁造成的上下文切换,体现了空间换时间的思想。

Timethreads图 - 01

3. 编程规约

记得吗?《阿里巴巴Java开发手册》中提到过关于ThreadLocal的编程规约,如下所示:

4. 使用场景


看到这里,相信你已经掌握了ThreadLocal的用法,下一篇文章将深入ThreadLocal的核心,探讨数据结构ThreadLocalMap的实现细节,欢迎关注彭旭锐的简书!


推荐阅读


参考


感谢喜欢!你的点赞是对我最大的鼓励!欢迎关注彭旭锐的简书!

上一篇下一篇

猜你喜欢

热点阅读