转载

ThreadLocal使用详解

ThreadLocal 主要解决多线程并发访问导致数据不一致问题。 ThreadLocal 为每一个使用该变量的线程都提供一个变量值的副本,虽然这种方式耗费内存,但是大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

举个例子:将 ThreadLocal 比喻成存放数据的盒子,盒子中只可以存储盒子主人的私有数据。由于数据空间是隔离的,因此,多线程并发访问而互不影响,从而避免了线程安全的问题。

ThreadLocal 是如何做到为每一个线程维护变量的副本?其实实现思路很简单,在 ThreadLocal 类中有一个 ThreadLocalMap ,用于存储每一个线程的变量的副本。

###ThreadLocal和Synchonized 的区别

  1. ThreadLocal :使用副本机制,使每个线程在某一时该访问到的对象是不同的,从而保证线程间的 数据隔离
  2. synchronized :使用锁机制,使对象在某一时只能被一个线程访问,从而保证线程间的 数据共享

代码示例

import java.util.*;

public class ThreadLocalDemo implements Runnable {

    // list 不是线程安全的,所以每个线程都要有自己独立的副本
    private static ThreadLocal<List> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalDemo obj = new ThreadLocalDemo();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(obj, "thread" + i);
            Thread.sleep(1000);
            t.start();
        }
    }

    @Override
    public void run() {
        List<String> list = new ArrayList<>();
        list.add(Thread.currentThread().getName());
        threadLocal.set(list);
        System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
        list.add("1");
        list.add("2");
        System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
    }

}
复制代码

ThreadLocal源码

  1. ThreadLocal 类的 get() 方法
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();
    }
复制代码

其中 ThreadLocalMap 可以理解为定制化的 HashMap ,而 ThreadLocal 只是对其进行了封装,并传递变量值,通过 ThreadLocalMapget() 方法获取当前线程局部变量的值。

  1. ThreadLocal类的 set() 方法
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
复制代码

实现原理和 get() 方法一样,内部由 ThreadLocalMap 管理数据,set 时先检查 map 是否为空,如果不为空就直接保存数据,如果为空则先创建 map 再保存数据。

ThreadLocal内存泄露问题

ThreadLocalMap 中使用的 key 为弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在GC 执行的时候会把 key 清理掉,而 value 不会被清理。这样一来, ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如不做任何措施的话,value 可能永远被 GC 回收,这个时候可能会产生内存泄漏。

以上情况, ThreadLocalMap 的实现已经考虑到了,在调用 set()get()remove() 方法时,会清理 key 为 null 的记录。(我们在使用完 ThreadLocal 后,最好手动调用 remove() 方法)

弱引用

如果一个对象只具有弱引用,那就属于可有可无的对象。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。垃圾回收器线程执行时会扫描它所管辖的内存区域,一旦发现只具有弱引用的对象,不管当前内存空间足够与否,都会将它回收。不过,由于垃圾回收期是一个优先级很低的线程,因此不一定很快就会发现那些只具有弱引用的对象。

弱引用可以和一个引用队列( ReferenceQueue )联合使用,如果弱引用对象被垃圾回收后,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

原文  https://juejin.im/post/5d1e9fa251882552903ca97c
正文到此结束
Loading...