一、ThreadLocal定义
ThreadLocal是一个可以提供线程局部变量的类,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路,通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用。作用:提供一个线程内公共变量(比如本次请求的用户信息),减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,或者为线程提供一个私有的变量副本,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
二、基本使用
1 public class ThreadLocalDemo { 2 public static ThreadLocal<Person> local = new ThreadLocal<Person>(){ 3 protected Person initialValue() { 4 return new Person(Thread.currentThread().getName()); 5 }; 6 }; 7 public static Person getPerson(){ 8 return local.get(); 9 } 10 11 public static void main(String[] args) { 12 for(int i=0;i<5;i++){ 13 new Thread(new Runnable() { 14 @Override 15 public void run() { 16 System.out.println(Thread.currentThread().getName()+":"+ThreadLocalDemo.getPerson().getName()); 17 } 18 }).start(); 19 } 20 System.out.println(Thread.currentThread().getName()+":"+ThreadLocalDemo.getPerson().getName()); 21 } 22 23 } 24 25 class Person{ 26 private String name; 27 28 public Person(String name){ 29 this.name = name; 30 } 31 public String getName() { 32 return name; 33 } 34 35 public void setName(String name) { 36 this.name = name; 37 } 38 39 }
结果输出:
main:main Thread-0:Thread-0 Thread-2:Thread-2 Thread-4:Thread-4 Thread-1:Thread-1 Thread-3:Thread-3
从结果可以看出,local对象针对不同的线程提供的Person变量是不同的。并且互不影响。同一个线程能够共享local中保存的对象。
三、源码分析
1.get()方法,用来获取当前调用get方法的线程对应在ThreadLocal中保存的变量的副本。
public T get() { Thread t = Thread.currentThread();//获取当前线程 ThreadLocalMap map = getMap(t);//通过线程获取对应的ThreadLocalMap,ThreadLocalMap是ThreadLocal中的一个静态内部类,可将他看做一个特殊的map,key是ThreadLocal,value则是保存的变量的值。 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
通过线程t获取,在Thread类中定义了ThreadLocal.ThreadLocalMap threadLocals = null;即将线程和ThreadLocalMap联系起来,初始时,threadLocals=null,则需要通过setInitialValue方法创建,具体如下:
private T setInitialValue() { T value = initialValue();//调用ThreadLocal中的initialValue方法获取变量的初始值 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);//当线程关联的ThreadLocalMap为null的时候,创建一个ThreadLocalMap,并赋值让线程t的threadLocals引用指向新创建的ThreadLocalMap对象。 return value; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
通过ThreadLocal的源码分析,可以知道每个线程都有一个ThreadLocalMap类型的引用threadLocals,线程初始时候为null,当我们调用set或者get方法的时候,通过当前线程获取ThreadLocalMap,如果不为null,则通过当前threadLocal作为key,获取对应的value值。如果为null,则new ThreadLocalMap(this, firstValue);,this指向ThreadLocal对象,firstValue则是initialValue()方法中指定的初始值。
2.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); } //通过线程类Thread获取关联的ThreadLocalMap对象,如果不为null,直接调用ThreadLocalMap的set方法,设置键值对,key为ThreadLocal对象,value则为设置的值。
3.remove()方法
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
四、应用场景
数据库连接,session管理,redis连接等。例如经典的使用,如下所示:
1 public class JdbcTemplateImpl { 2 // 数据库用户名 3 private String username; 4 // 数据库密码 5 private String password; 6 // 驱动信息 7 private String driver; 8 // 数据库地址 9 private String url; 10 // 声明线程共享变量 11 public static ThreadLocal<Connection> connections= new ThreadLocal<Connection>(); 12 13 public JdbcTemplateImpl(String driver, String url, String username, String password) { 14 this.driver = driver; 15 this.url = url; 16 this.username = username; 17 this.password = password; 18 try { 19 Class.forName(this.driver); 20 } catch (Exception e) { 21 e.printStackTrace(); 22 } 23 } 24 25 public Connection getConnection() { 26 Connection connection = connections.get(); 27 try { 28 if (connection != null && !connection.isClosed()) { 29 return connection; 30 }else { 31 connection = DriverManager.getConnection(this.url, this.username, this.password); 32 connections.set(connection); 33 } 34 } catch (SQLException e) { 35 e.printStackTrace(); 36 } 37 return connection; 38 } 39 }