ThreadLocal的底层原理及使用方式

ThreadLocal可以理解为主要解决多线程并发的问题,实际上,在使用场合上也多是在处理多线程并发的时候会用到ThreadLocal这个类。然而ThreadL并不是一个Thread,而是代表了线程变量的副本。ThreadLocal只对外开放了四个方法,分别是构造器ThreadLocal()、set()、get()、和remove(),当然,还有一个initialValue()方法是protected类型,这个方法应该是提供给开发人员去重写的。
ThreadLocal的使用方式一般是创建Threadlocal对象,然后调用他的方法。

ThreadLocal local=new ThreadLocal();

看一下set()的源码:

public void set(T value) {
         //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap,也就是Thread的threadLocals 变量
        ThreadLocalMap map = getMap(t);
        //如果不为空就将value放进去,为空就创建一个map
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

源码中的Thread的threadLocals 变量

 ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap可以看成一个HashMap,键为当前ThreadLocal的对象,值为我们要放入的值
getMap()的源码:

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

可以看到直接返回的是当前线程的threadLocals变量
createMap()的源码:

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

以上就是ThreadLocal的赋值操作的原理,现在来看一下get()方法的源码:

 public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取 ThreadLocalMap的实例
        ThreadLocalMap map = getMap(t);
        //如果不为空就调用   ThreadLocalMap的实体,否则就调用 setInitialValue()方法
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

看一下 setInitialValue()的源码:

 private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

可以看到 setInitialValue()是调用initialValue();生成一个初始值。
而initialValue()的源码:

 protected T initialValue() {
        return null;
    }

initialValue()直接返回了null,所以我们在创建ThreadLocal对象时,最好重写一下 initialValue()方法,设置一个默认初始值,在没有调用set()的情况下,避免出现空指针异常。

ThreadLocal的应用场景
先看一下不使用ThreadLocal,多线程下操作共享变量

private static int Count=0;
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<5;i++){
                    Count=Count+1;
                    System.out.println(Thread.currentThread().getName()+":"+Count);
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<5;i++){
                    Count=Count+1;
                    System.out.println(Thread.currentThread().getName()+":"+Count);
                }
            }
        }).start();

    }

运行结果:

Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
Thread-1:10

Process finished with exit code 0

可以看到这两个线程都使用了共享变量,但是没有处理多线程并发问题的处理方法,所以才会不符合我们的预期值。

使用ThreadLocal,多线程下操作共享变量:

public static void main(String[] args) {
        ThreadLocal local=new ThreadLocal(){
            protected  Integer initialValue(){
                return 0;
            }
        };
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<5;i++){
                    local.set(local.get()+1);
                    System.out.println(Thread.currentThread().getName()+":"+local.get());
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<5;i++){
                    local.set(local.get()+1);
                    System.out.println(Thread.currentThread().getName()+":"+local.get());
                }
            }
        }).start();

    }
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
Thread-1:5

Process finished with exit code 0

ThreadLocal和加锁的方式的比较
ThreadLocal和加锁的方式都是为了解决多线程对共享变量的并发问题
在加锁的方式中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线 程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

ThreadLocal的使用场景:
当 然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了 同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的 有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线 程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所 以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间 的共享冲突,可以使用ThreadLocal,这将极大地简化你的程 序,使程序更加易读、简洁。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

你可能感兴趣的:(ThreadLocal的底层原理及使用方式)