1.ThreadLocal的简单使用

从ThreadLocal的名字上可以看到,这是个线程的局部变量。也就是说只有当前线程可以访问,既然是只有当前线程池可以访问的数据自然是线程安全的。
package thread; import java.text.ParseException; import
java.text.SimpleDateFormat; import java.util.Date; import
java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock; public class ThreadLocalDemo {
private static ThreadLocal<SimpleDateFormat> t1 = new
ThreadLocal<SimpleDateFormat>(); //如果不想要下面的t1.set则可以通过这种方式构造ThreadLocal. //
private static ThreadLocal<SimpleDateFormat> t1 = new
ThreadLocal<SimpleDateFormat>() { // public SimpleDateFormat initialValue() {
// return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // } // }; private
static final SimpleDateFormat sm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static ReentrantLock lock = new ReentrantLock(); public static class
ParseDate implements Runnable { int i = 0; public ParseDate(int i) { this.i =
i; } // @Override // public void run() { // try { // lock.lock(); // Date date
= sm.parse("2018-8-12 17:30:" + i + ""); // System.out.println(i + "--" +
date); // lock.unlock(); // Thread.sleep(1000); // } catch (ParseException e) {
// // TODO Auto-generated catch block // e.printStackTrace(); // } catch
(InterruptedException e) { // // TODO Auto-generated catch block //
e.printStackTrace(); // } // } // @Override // public void run() { // try { //
synchronized (ParseDate.class) { // Date date = sm.parse("2018-8-12
17:30:"+i+""); // System.out.println(i+"--"+date); // } // Thread.sleep(1000);
// } catch (ParseException e) { // // TODO Auto-generated catch block //
e.printStackTrace(); // } catch (InterruptedException e) { // // TODO
Auto-generated catch block // e.printStackTrace(); // } // } @Override public
void run() { if (t1.get() == null) t1.set(new SimpleDateFormat("yyyy-MM-dd
HH:mm:ss")); try { Date date = t1.get().parse("2018-8-12 17:30:" + i + "");
System.out.println(i + "--" + date); Thread.sleep(1000); } catch
(ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); }
catch (InterruptedException e) { // TODO Auto-generated catch block
e.printStackTrace(); } } } public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10); for (int i = 0; i < 30;
i++) es.execute(new ParseDate(i)); } }


可以看到输出结果也是正确地。这里我有三种方式保证线程的安全性,锁和重入锁还有就是ThreadLocal。在线程ThreadLocal中if
(t1.get() == null)
                t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));


这两行代码的含义是,如果当前线程不持有SimpleDateFormat对象的实例则新建一个并将其设置到当前线程中。如果有则直接使用。从这里也可以看到为每个线程人手分配一个对象的工作并不是由ThreadLocal来完成的而是在应用层保证的。如果在应用上为每一个线程分配相同的对象实例,那么ThreadLocal也不能保证线程安全。(注意:为每个线程分配不同的对象,需要在应用层保证。ThreadLocal只是起到简单容器的作用。)

2.ThreadLocal的实现原理
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap
map = getMap(t); if (map != null) map.set(this, value); else createMap(t,
value); }

首先它会获取当前线程对象,然后通过getMap()拿到线程ThreadLocalMap,然后将值设入ThreadLocalMap中。(ThreadLocalMap可以简单的理解为一个Map,它是定义在Thread内部的成员。)




设置到ThreadLocal中的数据,也是写入了threadLocals这个Map中。其中key为ThreadLocal当前对象,value则是我们需要的值这里是:new
SimpleDateFormat("yyyy-MM-dd
HH:mm:ss")。而threadLocals本身就保存了当前自己所在线程的所有局部变量,也就是一个ThreadLocal变量的集合。

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(); }
它也是先获取当前线程的ThreadLocalMap对象,然后通过将自己作为key取得内部的实际数据。

因为这些变量是维护在Thread类内部的,所以这意味着只要线程不退出,对象的引用将一直存在。而当线程退出时,Thread类会进行清理工作:




因此如果使用线程池,这就意味着当前线程未必会退出(比如固定大小的线程池。)如果这样将一些太大的对象设置到ThreadLocal中(实际会保存在线程持有的threadLocals
Map中)。可能会使系统出现内存泄露的可能。


此时如果希望及时的回收对象,最好使用ThreadLocal.remove()方法将这个变量移除。或者可以直接通过将ThreadLocal对象设为null的方式,让系统自行垃圾回收。

ThreadLocal的使用场景一般是共享对象对于竞争的处理容易引起性能的损失,这时候可以考虑使用ThreadLocal为每个线程分配单独的对象。

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信