本文带领你从各个方面了解并掌握ThreadLocal,进而彻底精通。
使用场景
1.每个线程需要一个独享的对象(不安全的工具类)
2.每个线程需要保存全局变量(可让不同的方法调用)
以下实例代码对共享的操作添加了类锁,会有性能问题,见下图
package threadPool; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 测试不使用ThreadLocal时如何对线程的共享变量加锁 */ public class NotUseThreadLocalTest01 { SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); public static void main(String[] args) { //创建线程池去执行1000个任务 ExecutorService executorService= Executors.newFixedThreadPool(10); NotUseThreadLocalTest01 threadLocalTest01=new NotUseThreadLocalTest01(); for (int i=0;i<1000;i++){ int finalI = i; executorService.submit(new Runnable() { @Override public void run() { //获取当前时间戳 System.out.println(threadLocalTest01.getDateStrFromSeconds(finalI)); } }); } executorService.shutdown(); } public String getDateStrFromSeconds(int seconds){ Date date=new Date(seconds*1000); String dateStr=null; //加类锁 synchronized (NotUseThreadLocalTest01.class){ dateStr=simpleDateFormat.format(date); } return dateStr; } }
以下实例代码我们使用threadlocal去生成dateformat,见下文。
package threadPool; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 测试ThreadLocal时如何对线程的共享变量加锁 */ public class UseThreadLocalTest02 { public static void main(String[] args) { //创建线程池去执行1000个任务 ExecutorService executorService= Executors.newFixedThreadPool(10); UseThreadLocalTest02 threadLocalTest01=new UseThreadLocalTest02(); for (int i=0;i<1000;i++){ int finalI = i; executorService.submit(new Runnable() { @Override public void run() { //获取当前时间戳 System.out.println(threadLocalTest01.getDateStrFromSeconds(finalI)); } }); } executorService.shutdown(); } public String getDateStrFromSeconds(int seconds){ Date date=new Date(seconds*1000); return DateFormatter.simpleDateFormatThreadLocal.get().format(date); } } class DateFormatter{ public static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal=new ThreadLocal<SimpleDateFormat>(){ @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); } }; //也可以使用lamda初始化 public static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocalWithLamda=ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss") ); }
以下实现,我们对线程中不同的方法基于threadlocal传递参数,见下图
package threadPool; /** * 实现主线程,基于threadlocal传递参数 */ public class UserThreadLocal03 { public static void main(String[] args) { try{ UserThreadLocal03 userThreadLocal03=new UserThreadLocal03(); userThreadLocal03.serviceOne(); }finally { ThreadLocalParams.userThreadLocal.remove(); } } public void serviceOne(){ ThreadLocalParams.userThreadLocal.set(new User("123")); serviceTwo(); } public void serviceTwo(){ System.out.println("userId:"+ThreadLocalParams.userThreadLocal.get().getId()); serviceThree(); } public void serviceThree(){ System.out.println("userId:"+ThreadLocalParams.userThreadLocal.get().getId()); } } class User { private String id; public User(String id) { this.id = id; } public String getId() { return id; } public void setId(String id) { this.id = id; } } class ThreadLocalParams{ public static ThreadLocal<User> userThreadLocal=new ThreadLocal<User>(); }
ThreadLocal方法介绍
1.initialValue:初始化
2.get 获取当前线程threadlocal对应的值
3.set 设置当前线程threadLocal的值
4.remove移除
ThreadLocal部分源码分析
主要分析initialValue、get、set等
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(); }
get方法首先会从当前threadlocalMap中获取,如果有了,直接返回,若没有的时候,则会调用initialValue方法后返回,则说明initialValue这个方法是延迟加载的,只有在用的时候才会加载,接下面我们查看下set方法的部分实现。
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方法最重要的调用方法为map.set,将线程变量的值进行写入,接下面我们查看下initialValue方法,
initialValue方法部分实现如下图所示:
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方法最终调用的也是map.set方法,进而可知,initialValue和set的实现有异曲同工之妙。
ThreadLocal内存泄漏问题
我们查看remove代码可以看到下面这样的实现,会执行clear操作,所有在线程池中传递变量后,不再使用时,需要显示执行remove等类似方法进行清除。
private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } } ............. public void clear() { this.referent = null; } ............. private T referent; /* Treated specially by GC */
ThreadLocal优点
1.解决线程安全问题
2.减少内存的使用
3.避免或者减少线程方法中的方法传递
ThreadLocal空指针异常
详细介绍空指针异常。threadLocal本身没有空指针异常, 多数情况是类型进行自动装箱时导致的,可以看下实例代码,如下所示:
package threadPool; public class ThreadLocalNullPointExceptionTest { private ThreadLocal<Long> threadLocal= new ThreadLocal<Long>(); public void set(){ threadLocal.set(Thread.currentThread().getId()); } /** * 空的null转成long的时候会出现空指针异常 * Exception in thread "main" java.lang.NullPointerException * at threadPool.ThreadLocalNullPointExceptionTest.get(ThreadLocalNullPointExceptionTest.java:9) * at threadPool.ThreadLocalNullPointExceptionTest.main(ThreadLocalNullPointExceptionTest.java:14) * @return */ public long get(){ return threadLocal.get(); } public static void main(String[] args) { ThreadLocalNullPointExceptionTest threadLocalNullPointExceptionTest=new ThreadLocalNullPointExceptionTest(); System.out.println(threadLocalNullPointExceptionTest.get()); } }