Java 线程安全

1. 什么是线程安全?

  可变资源(内存)间线程共享。

2. 如何实现线程安全?

  • 不共享资源;
  • 共享不可变资源;
  • 共享可变资源:
    • 可见性;
    • 操作原子性;
    • 禁止重排序;

  1. 不共享资源

    1. 可重入函数:函数体内部不涉及任何外部变量;

// 可重入函数
public static int addTwo(int num) {
    return  num + 3;
}

    2. 使用ThreadLoacl:

final static ThreadLocal<String> mThreadLocal = new ThreadLocal<>();

public static void test() {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            mThreadLocal.set(UUID.randomUUID().toString());

            System.out.println(Thread.currentThread().getName() + " -- token : " + mThreadLocal.get());
        }
    };

    for (int idx = 0; idx < 3; idx++) {
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

   上面的结果就是每个线程都有自己的副本,输出的值也不相同。

 

  ThreadLocal作用及实现原理:

  ThreadLocal作用是用于线程间数据隔离,ThreadLocal是线程内部数据存储类,通过ThreadLocal将数据存储在指定线程中,存储后只能通过指定线程获取数据,其它线程是获取不到数据的。ThreadLocal是一个Map(ThreadLocalMap),Key是线程对象Thread.currentThread,值为存储的数据,在Hander的Looper类中的ThreadLocal存储的是Looper对象。

  在Looper中ThreadLocal是静态常量存储于静态存储区,线程中工作内存中使用的是ThreadLocal的主内存的副本,但是由于ThreadLocal是一个Map对象,Key又是线程(Thread.currentThread),只能通过指定线程获取ThreadLocal中的数据,所以,做到了线程间数据隔离互不影响。

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

  通过ThreadLocal源码中get()和set()方法,操作的变量是ThreadLocalMap。

  ThreadLocalMap的创建:

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

  ThreadLocalMap就是一个Map,而向Map中存储数据的key是Thread的this(map.set(this, value);),就是将值存储到线程上的,就是通过Map将thread的this和value绑定。

  ThreadLocal使用建议:

    • 声明为全局静态final变量;
    • 避免存储大量对象;
    • 使用完后及时移除对象;

  2. 共享不可变资源

    使用final关键字

  3. 共享可变资源

    1. 禁止重排序特性;

    2. 可见性:

      使用final和volatile关键字:

      final:1. 修饰变量不可被修改;2. 修饰类不可被继承;3. 修饰方法不可被覆写;4. 禁止重排序特性;

      volatile:1. 可见性 -- 多线程操作时,其中一个线程修改了volatile修饰的变量,会通知其它线程变量改变;2. 禁止重排序;

      PS:每个线程都有自己的工作内存,工作内存将主内存变量copy到工作内存后,再执行操作。所以,导致了多线程时变量数据不一致性。

      2. 加锁:锁释放后强制将缓存数据刷新到主内存。

    3. 操作原子性:加锁。

上一篇:ThreadLocal的一道有意思的题


下一篇:浅析PageHelper踩坑:不安全分页导致的问题