ThreadLocal使用及原理

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为每个线程分配单独的对象。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值