ThreadLocal的简单使用和实现原理
我们先看以下代码,不用ThreadLocal会发生什么情况
package com.qjc.thread.threadLocal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //ThreadLocal顾名思义表示线程的局部变量,及只有当前线程可以访问,自然是线程安全的 public class ThreadLocalTest { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static class ParseDate implements Runnable { int i = 0; public ParseDate(int i) { this.i = i; } @Override public void run() { try { Date parse = sdf.parse("2018-04-19 15:12:" + i % 60); System.out.println(i + ":" + parse); } catch (ParseException e) { e.printStackTrace(); } } } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { executorService.execute(new ParseDate(i)); } executorService.shutdown(); } }
控制台输出了这么一个异常
很明显是异常出现在线程上,表明这样做是线程不安全的
下面,我们用ThreadLocal,代码如下:
package com.qjc.thread.threadLocal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //ThreadLocal顾名思义表示线程的局部变量,及只有当前线程可以访问,自然是线程安全的 public class ThreadLocalTest { // private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>(); public static class ParseDate implements Runnable { int i = 0; public ParseDate(int i) { this.i = i; } @Override public void run() { try { // 用ThreadLocal if (threadLocal.get() == null) { threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); } Date parse = threadLocal.get().parse("2018-04-19 15:12:" + i % 60); // 不用ThreadLocal // Date parse = sdf.parse("2018-04-19 15:12:" + i % 60); System.out.println(i + ":" + parse); } catch (ParseException e) { e.printStackTrace(); } } } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { executorService.execute(new ParseDate(i)); } executorService.shutdown(); } }
控制台,正常输出,没有异常出现,表明线程安全。
我们分析一下ThreadLocal的实现原理:
public void set(T value) { Thread t = Thread.currentThread();//获取当前线程对象 ThreadLocalMap map = getMap(t);//拿到线程的ThreadLocalMap,它是Thread内部的成员(Thread类中的):ThreadLocal.ThreadLocalMap threadLocals = null; //并将值设入ThreadLocalMap if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue);//从此可以看出,ThreadLocalMap的key就是ThreadLocal当前对象,value就是我们设置的值 }
public T get() { Thread t = Thread.currentThread();//获取当前线程对象 ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap对象 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this);//然后通过将自己作为key取得内部的实际数据 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }