java-----ThreadLocal源码分析

      前面Handler消息处理机制中提到了线程会将自己的Looper对象放到ThreadLocal中,因而我们有必要看看ThreadLocal是什么?

      ThreadLocal是什么?

      ThreadLocal也是用来进行多线程并发的,可以理解为是线程的局部变量,作用就是为每个线程提供一个变量值的副本,每个线程可以独立的改变自己的副本而不影响其他线程。

      ThreadLocal与synchronized的区别?

      两者都可以实现多线程的并发

      ThreadLocal:采用空间换时间的方式

      synchronized:采用时间换空间的方式

      ThreadLocal是怎么做到为每个线程维护变量副本的呢?

      每个线程内部都有一个ThreadLocalMap的变量,用来存储每个变量的副本,而ThreadLocalMap采用Entry数组来存储ThreadLocal---Object键值对;

      在分析源码前,先来看看我们平常使用多线程的例子

      实例1:

public class ThreadLocalTest {
	public static void main(String[] args) {
		Test test = new Test(0);
		MyThread thread1 = new MyThread(test);
		MyThread thread2 = new MyThread(test);
		thread1.start();
		thread2.start();
	}
}
class MyThread extends Thread
{
	public Test test;
	public MyThread() {
	}
	public MyThread(Test test)
	{
		this.test = test;
	}
	@Override
	public void run() {
		for(int i = 0;i < 3;i++)
		{
			System.out.println(Thread.currentThread().getName()+"  value: "+test.getNum());
		}
	}
}
class Test 
{
	public int count;
	public Test(int count)
	{
		this.count = count;
	}
	public int getNum()
	{
		return count++;
	}
}
输出:

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

可以发现线程0和线程1是交错的在改变count值的,因为两个线程共用Test里面的count变量,而且两者改变的顺序是不固定的;

实例2:(使用ThreadLocal后的情况)

public class ThreadLocalTest {
	public static void main(String[] args) {
		Test test = new Test();
		MyThread thread1 = new MyThread(test);
		MyThread thread2 = new MyThread(test);
		thread1.start();
		thread2.start();
	}
}
class MyThread extends Thread
{
	public Test test;
	public MyThread() {
	}
	public MyThread(Test test)
	{
		this.test = test;
	}
	@Override
	public void run() {
		for(int i = 0;i < 3;i++)
		{
			System.out.println(Thread.currentThread().getName()+"  value: "+test.getNum());
		}
	}
}
class Test 
{
	public ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>()
			{
		         protected Integer initialValue() 
		         {
		        	 return 0;
		         };
			};
	public int getNum()
	{
		threadLocal.set(threadLocal.get()+1);
		return threadLocal.get();
	}
}
输出:

Thread-1  value: 1
Thread-0  value: 1
Thread-1  value: 2
Thread-0  value: 2
Thread-1  value: 3
Thread-0  value: 3

发现线程0和1的输出并不是互相影响的,虽然他们共用test,但是对于count变量,他们各自都有自己的备份,修改的时候并不会影响另一个线程;

如果实例2还不明显的话,我们再来一个实例看看:

实例3:

public class ThreadLocalTest {
	public static void main(String[] args) {
		final Test test = new Test();
		test.init();//设置ThreadLocal里面的值
		System.out.println("ThreadID:  "+test.intThreadLocal.get());
		System.out.println("ThreadName:  "+test.stringThreadLocal.get());
		new Thread(){
			public void run() 
			{
				test.init();//设置ThreadLocal里面的值
				System.out.println("ThreadID:  "+test.intThreadLocal.get());
				System.out.println("ThreadName:  "+test.stringThreadLocal.get());
			};
		}.start();
		//子线程执行结束之后休眠5秒钟,为了查看子线程中的ThreadLocal值是否与主线程相关
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("ThreadID:  "+test.intThreadLocal.get());
		System.out.println("ThreadName:  "+test.stringThreadLocal.get());
	}
}
class Test 
{
	ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>();
	ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>();
	public void init()
	{
		intThreadLocal.set((int)Thread.currentThread().getId());
		stringThreadLocal.set(Thread.currentThread().getName());
	}
}
输出:

ThreadID:  1
ThreadName:  main
ThreadID:  9
ThreadName:  Thread-0
ThreadID:  1
ThreadName:  main

可以发现main中我们首先输出了主线程的ThreadID以及主线程的名字,随后开启子线程输出子线程的ThreadID和子线程的名字,随后休眠程序5秒钟直接输出主线程的ThreadID以及主线程的名字,在这次输出时我们并没有通过test.init( )初始化ThreadLocal的值,但是他仍然会输出主线程的信息,这说明一点,只要设置了线程的ThreadLocal值之后,及时有别的线程开启也不会影响原来现成的ThreadLocal,也就是说ThreadLocal是线程独享的;

下面从源码角度进行分析:

ThreadLocal类提供了4种主要方法:

public T get() {}
public void set(T value) {}
public void remove() {}
protected T initialValue() {}
剩下的方法都是让这四种方法间接调用的,在JDK1.2之前,方法里面的T全部是Object

下面分别讲解每个方法的实现:

get( ):

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
解释:

get方法首先会获取当前线程,然后通过getMap方法获取到当前线程的ThreadLocalMap属性值,来看看getMap(Thread thread)方法:

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
解释:
getMap很简单,就是直接返回当前线程的属性值,也就是Thread里面肯定有threadLocals属性,果不其然

class Thread implements Runnable {
	 ThreadLocal.ThreadLocalMap threadLocals = null;
}

那么ThreadLocalMap又是什么东西呢?

他是ThreadLocal的静态内部类,源码如下:

static class ThreadLocalMap {
	
    static class Entry extends WeakReference<ThreadLocal> {
        Object value;
        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }
}
这个静态内部类里面又有一个静态内部类Entry,实现了WeakReference类,并且在类中将ThreadLocal通过super(k)调用WeakReference的构造函数将当前ThreadLocal设置成软引用,便于GC及时回收ThreadLocal占用的内存,从这里也就看出来ThreadLocalMap其实就是一个以ThreadLocal---Object为键值的Map;

回到get方法,在获取到ThreadLocalMap之后,接下来分两种情况讨论,(1)如果map不为空的话,通过ThreadLocalMap的getEntry方法获取到当前ThreadLocal的键值对,这里getEntry传入的参数是ThreadLocal变量而不是当前线程,接着如果Entry值不为空的话,返回Entry的value值即可;(2)如果map为空的话,调用setInitialValue来生成一个具有默认值的ThreadLocal,并且返回这个值;

看下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;
    }
解释:

首先会调用initialValue方法来获得设置的初始值,这个方法是protected修饰的,因此实际中是由我们自己来重写的,方法原型是:

protected T initialValue() {
        return null;
    }
随后获取当前线程的ThreadLocalMap属性值,如果这个值不为null的话,则将默认value值设置到当前的ThreadLocal中,如果这个值为空的话,则调用createMap创建一个ThreadLocalMap,他的初始里面仅仅包含一个键值对,键为当前的ThreadLocal,值为

initialValue设置的初始值,最后返回初始值即可;

好了,get方法分析完毕,其实他就是返回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);
    }
看到没有呢?其实set方法的实现和i nitialValue方法几乎是一样的,只不过一个是我们在使用的过程中设置的,一个是我们在初始化的时候设置的;

remove( )方法:

   public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
也很简单,调用ThreadLocalMap的remove方法,从当前线程的ThreadLocalMap中移除当前ThreadLocal;
总结一下:

(1)一个Thread中可以有多个ThreadLocal,他们是作为键存储在ThreadLocalMap中的,值为变量的值;

(2)我们在使用ThreadLocal的get方法之前要不先调用set方法,设置ThreadLocal的值,要不重写ThreadLocal的protected修饰的initialValue方法来初始化ThreadLocal值;否则会发生空指针异常;

实例4:

public class ThreadLocalTest {
	public static void main(String[] args) {
		final Test test = new Test();
		test.init();//设置ThreadLocal里面的值
		System.out.println("ThreadID:  "+test.intThreadLocal.get());
		System.out.println("ThreadName:  "+test.stringThreadLocal.get());
	}
}
class Test 
{
	ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>();
	ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>();
	public int getInt()
	{
		return intThreadLocal.get();
	}
	public void init()
	{
		System.out.println(getInt());
		intThreadLocal.set((int)Thread.currentThread().getId());
		stringThreadLocal.set(Thread.currentThread().getName());
	}
}
发现会在return intThreadLocal.get()这行报空指针异常;

解决:

public class ThreadLocalTest {
	public static void main(String[] args) {
		final Test test = new Test();
		test.init();//设置ThreadLocal里面的值
		System.out.println("ThreadID:  "+test.intThreadLocal.get());
		System.out.println("ThreadName:  "+test.stringThreadLocal.get());
	}
}
class Test 
{
	ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>(){
		protected Integer initialValue() {
			return 0;
		};
	};
	ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>();
	public int getInt()
	{
		return intThreadLocal.get();
	}
	public void init()
	{
		System.out.println(getInt());
		intThreadLocal.set((int)Thread.currentThread().getId());
		stringThreadLocal.set(Thread.currentThread().getName());
	}
}
这样子就不会报空指针异常啦,原因很简单,我们在在get方法的源码分子中已经看到,如果当前ThreadLocalMap为空的话,会调用setInitialValue方法进行初始化,进而会调用到initialValue方法,这也就是protected方法,只要我们重写就可以设置我们想要初始化的值啦;

好了,ThreadLocal源码分析完毕,有什么错误还希望各位指正;







  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值