JAVA之ThreadLocal

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

ThreadLocal是什么?

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

ThreadLocal与synchronized的区别?

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

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

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

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

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

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

实例1:

[java] view plain copy

  1. public class ThreadLocalTest {
  2. public static void main(String[] args) {
  3. Test test = new Test(0);
  4. MyThread thread1 = new MyThread(test);
  5. MyThread thread2 = new MyThread(test);
  6. thread1.start();
  7. thread2.start();
  8. }
  9. }
  10. class MyThread extends Thread
  11. {
  12. public Test test;
  13. public MyThread() {
  14. }
  15. public MyThread(Test test)
  16. {
  17. this.test = test;
  18. }
  19. @Override
  20. public void run() {
  21. for(int i = 0;i < 3;i++)
  22. {
  23. System.out.println(Thread.currentThread().getName()+"  value: "+test.getNum());
  24. }
  25. }
  26. }
  27. class Test
  28. {
  29. public int count;
  30. public Test(int count)
  31. {
  32. this.count = count;
  33. }
  34. public int getNum()
  35. {
  36. return count++;
  37. }
  38. }

输出:

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后的情况)

[java] view plain copy

  1. public class ThreadLocalTest {
  2. public static void main(String[] args) {
  3. Test test = new Test();
  4. MyThread thread1 = new MyThread(test);
  5. MyThread thread2 = new MyThread(test);
  6. thread1.start();
  7. thread2.start();
  8. }
  9. }
  10. class MyThread extends Thread
  11. {
  12. public Test test;
  13. public MyThread() {
  14. }
  15. public MyThread(Test test)
  16. {
  17. this.test = test;
  18. }
  19. @Override
  20. public void run() {
  21. for(int i = 0;i < 3;i++)
  22. {
  23. System.out.println(Thread.currentThread().getName()+"  value: "+test.getNum());
  24. }
  25. }
  26. }
  27. class Test
  28. {
  29. public ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>()
  30. {
  31. protected Integer initialValue()
  32. {
  33. return 0;
  34. };
  35. };
  36. public int getNum()
  37. {
  38. threadLocal.set(threadLocal.get()+1);
  39. return threadLocal.get();
  40. }
  41. }

输出:

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:

[java] view plain copy

  1. public class ThreadLocalTest {
  2. public static void main(String[] args) {
  3. final Test test = new Test();
  4. test.init();//设置ThreadLocal里面的值
  5. System.out.println("ThreadID:  "+test.intThreadLocal.get());
  6. System.out.println("ThreadName:  "+test.stringThreadLocal.get());
  7. new Thread(){
  8. public void run()
  9. {
  10. test.init();//设置ThreadLocal里面的值
  11. System.out.println("ThreadID:  "+test.intThreadLocal.get());
  12. System.out.println("ThreadName:  "+test.stringThreadLocal.get());
  13. };
  14. }.start();
  15. //子线程执行结束之后休眠5秒钟,为了查看子线程中的ThreadLocal值是否与主线程相关
  16. try {
  17. Thread.sleep(5000);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. System.out.println("ThreadID:  "+test.intThreadLocal.get());
  22. System.out.println("ThreadName:  "+test.stringThreadLocal.get());
  23. }
  24. }
  25. class Test
  26. {
  27. ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>();
  28. ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>();
  29. public void init()
  30. {
  31. intThreadLocal.set((int)Thread.currentThread().getId());
  32. stringThreadLocal.set(Thread.currentThread().getName());
  33. }
  34. }

输出:

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种主要方法:

[java] view plain copy

  1. public T get() {}
  2. public void set(T value) {}
  3. public void remove() {}
  4. protected T initialValue() {}

剩下的方法都是让这四种方法间接调用的,在JDK1.2之前,方法里面的T全部是Object

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

get( ):

[java] view plain copy

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null)
  7. return (T)e.value;
  8. }
  9. return setInitialValue();
  10. }

解释:

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

[java] view plain copy

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

解释:
getMap很简单,就是直接返回当前线程的属性值,也就是Thread里面肯定有threadLocals属性,果不其然

[java] view plain copy

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

那么ThreadLocalMap又是什么东西呢?

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

[java] view plain copy

  1. static class ThreadLocalMap {
  2. static class Entry extends WeakReference<ThreadLocal> {
  3. Object value;
  4. Entry(ThreadLocal k, Object v) {
  5. super(k);
  6. value = v;
  7. }
  8. }
  9. }

这个静态内部类里面又有一个静态内部类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的源码:

[java] view plain copy

  1. private T setInitialValue() {
  2. T value = initialValue();
  3. Thread t = Thread.currentThread();
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null)
  6. map.set(this, value);
  7. else
  8. createMap(t, value);
  9. return value;
  10. }

解释:

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

[java] view plain copy

  1. protected T initialValue() {
  2. return null;
  3. }

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

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

好了,get方法分析完毕,其实他就是返回ThreadLocal在当前线程中保存的变量副本而已;

set( )方法:

[java] view plain copy

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }

看到没有呢?其实set方法的实现和initialValue方法几乎是一样的,只不过一个是我们在使用的过程中设置的,一个是我们在初始化的时候设置的;

remove( )方法:

[java] view plain copy

  1. public void remove() {
  2. ThreadLocalMap m = getMap(Thread.currentThread());
  3. if (m != null)
  4. m.remove(this);
  5. }

也很简单,调用ThreadLocalMap的remove方法,从当前线程的ThreadLocalMap中移除当前ThreadLocal;
总结一下:

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

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

实例4:

[java] view plain copy

  1. public class ThreadLocalTest {
  2. public static void main(String[] args) {
  3. final Test test = new Test();
  4. test.init();//设置ThreadLocal里面的值
  5. System.out.println("ThreadID:  "+test.intThreadLocal.get());
  6. System.out.println("ThreadName:  "+test.stringThreadLocal.get());
  7. }
  8. }
  9. class Test
  10. {
  11. ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>();
  12. ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>();
  13. public int getInt()
  14. {
  15. return intThreadLocal.get();
  16. }
  17. public void init()
  18. {
  19. System.out.println(getInt());
  20. intThreadLocal.set((int)Thread.currentThread().getId());
  21. stringThreadLocal.set(Thread.currentThread().getName());
  22. }
  23. }

发现会在return intThreadLocal.get()这行报空指针异常;

解决:

[java] view plain copy

  1. public class ThreadLocalTest {
  2. public static void main(String[] args) {
  3. final Test test = new Test();
  4. test.init();//设置ThreadLocal里面的值
  5. System.out.println("ThreadID:  "+test.intThreadLocal.get());
  6. System.out.println("ThreadName:  "+test.stringThreadLocal.get());
  7. }
  8. }
  9. class Test
  10. {
  11. ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>(){
  12. protected Integer initialValue() {
  13. return 0;
  14. };
  15. };
  16. ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>();
  17. public int getInt()
  18. {
  19. return intThreadLocal.get();
  20. }
  21. public void init()
  22. {
  23. System.out.println(getInt());
  24. intThreadLocal.set((int)Thread.currentThread().getId());
  25. stringThreadLocal.set(Thread.currentThread().getName());
  26. }
  27. }

这样子就不会报空指针异常啦,原因很简单,我们在在get方法的源码分子中已经看到,如果当前ThreadLocalMap为空的话,会调用setInitialValue方法进行初始化,进而会调用到initialValue方法,这也就是protected方法,只要我们重写就可以设置我们想要初始化的值啦;

时间: 2024-08-08 22:07:34

JAVA之ThreadLocal的相关文章

Java之ThreadLocal原理分析

简介 早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程程序.当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本.从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思.所以,在Java中编写线程局部变量

java concurrency: ThreadLocal及其实现机制

转载:http://shmilyaw-hotmail-com.iteye.com/blog/1703382 ThreadLocal概念 从字面上来理解ThreadLocal,感觉就是相当于线程本地的.我们都知道,每个线程在jvm的虚拟机里都分配有自己独立的空间,线程之间对于本地的空间是相互隔离的.那么ThreadLocal就应该是该线程空间里本地可以访问的数据了.ThreadLocal变量高效地为每个使用它的线程提供单独的线程局部变量值的副本.每个线程只能看到与自己相联系的值,而不知道别的线程可

深入研究java.lang.ThreadLocal类

深入研究java.lang.ThreadLocal类 一.概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为ThreadLocalVar更加合适.线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副

从Linux的errno到Java的ThreadLocal

Linux里的errno 在Linux下执行系统调用时,一般会有一个返回值表示成功或失败,但是这个值只说明了成功或失败,却没有说明是如何成功或失败的. errno就是为了解决这个问题的,系统调用会把错误号设置为errno,我们通过错误号就能知道失败的原因.还可以使用strerror打印出这个错误号对应的字符串说明.老版本的Linux需要加上extern int errno,现在直接引入<errno.h>就行了. errno示例: 现在的问题来了.假如我有两个线程,代码如下: errno是一个全

java的ThreadLocal类的用法

java的ThreadLocal类的用法,ThreadLocal是一个支持泛型的类,用在多线程中用于防止并发冲突问题. 例如下面的一个例子,就是用于线程增加1,但是相互不冲突 package com.test.threadlocal; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class Local { private static ThreadLocal<Integer

Java中ThreadLocal的深入理解

官方对ThreadLocal的描述: "该类提供了线程局部(thread-local)变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其get或set方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本.ThreadLocal实例通常是类中的private static字段,它们希望将状态与某一个线程(例如,用户ID或事物ID)相关联." <Thinking in Java>中的描述: 防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享.线程本地

帮助理解Java中ThreadLocal的一篇文章

原文地址:http://www.ahlinux.com/java/20332.html 并发编程中,一个重要的内容是数据共享.当你创建了实现Runnable接口的线程,然后开启使用相同Runnable实例的各种Thread对象,所有的线程便共享定义在Runnable对象中的属性.也就是说,当你在一个线程中改变任意属性时,所有的线程都会因此受到影响,同时会看到第一个线程修改后的值.有时我们希望如此,比如:多个线程增大或减小同一个计数器变量:但是,有时我们希望确保每个线程,只能工作在它自己的线程实例

转!! Java中ThreadLocal的设计与使用

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各个线程中访问的是不同的对象. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本.通过ThreadLocal.set()将这个新创建

浅谈对Java中ThreadLocal类的理解

首先要明确:ThreadLocal不是一个多线程类,或者应该叫做线程局部变量.这从ThreadLocal的JDK定义中就可以看到 public class ThreadLocal<T>extends Object 可以看出ThreadLocal只是一个普普通通的类,并没有继承自Thread或实现Runnable接口. 同时也可以看到ThreadLocal使用了泛型,这样他就可以操作几乎任何类型的数据了.下面说JDK API代码时具体再说. 对此类,看看JDK API中的部分描述: 该类提供了线