ThreadLocal使用详解

ThreadLocal简介

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

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

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

###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 再保存数据。ide

ThreadLocal内存泄露问题

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

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

弱引用

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

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

相关文章
相关标签/搜索