前言
在并发开发的过程中,我们都知道需要保证共享资源的的读写有序。加锁是我们比较常用的一种方式。ThreadLock则是从另外一个角度出发,每一个线程都独立资源,这样同样可以解决资源的问题。这样讲可能不是很好理解,下面我们通过案例来说明这个情况。
案例
我们在使用日期格式转换的时候,会出现日期转换出错,或者日期不是自己想要的结果。
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadLockTest { 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 f = sdf.parse("2017-01-15 15:22:" + i/60); System.out.println(i + ":" + f); } catch (ParseException e) { e.printStackTrace(); } } } public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i ++) { es.execute(new ParseDate(i)); } } }
运行结果:
SimpleDateFormat 本身不是线程安全的类,所以在这样的情况,我们直接这样使用是会出现问题的。
当然如果我们直接在线程里面new SimpleDateFormat来处理也是可以的,但是这样的话,因为日期处理一般系统都比较多的一种操作,每次都创建对象,就更加容易让Jvm出现OOM的情况。还有一种方式,就是每一个线程里面我们都创建一个SimpleDateFormat来处理线程内部的日期格式化。我们来一起看看如何实现:
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadLockTest1 { static ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<>(); public static class ParseDate implements Runnable { int i = 0; public ParseDate(int i) { this.i = i; } @Override public void run() { try { if (t1.get() == null) { // 当前线程没有SimpleDateFormat对象,则创建一个 t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); } Date f = t1.get().parse("2017-01-15 15:22:" + i/60); System.out.println(i + ":" + f); } catch (ParseException e) { e.printStackTrace(); } } } public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i ++) { es.execute(new ParseDate(i)); } } }
运行结果:
每一线程执行都正常了。
原理
看看我们用到的两个方法的源码实现逻辑:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
将对象存放到本地线程中。
public T get() { Thread t = Thread.currentThread(); ThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
获取对象的时候也从本地线程中获取。
重点:我们现在系统基本都使用线程池,如果你设置到线程里面的对象会导致GC无法回收。
如需及时回收对象,则可以使用
ThreadLocal.remove(); -- 清空线程中的对象信息。
总结
该功能用处并不会很广,比较小众的使用场景,了解即可。
参考:https://blog.csdn.net/mzh1992/article/details/62230075