ThreadLocal浅析

WeakReference如字面意思,弱引用, 当一个对象仅仅被weak reference(弱引用)指向, 而没有任何其他strong reference(强引用)指向的时候,
 如果这时GC运行, 那么这个对象就会被回收,不论当前的内存空间是否足够,这个对象都会被回收。

ThreadLocal 是线程的局部变量, 是每一个线程所单独持有的,其他线程不能对其进行访问, 通常是类中的 private static 字段,是对该字段初始值的一个拷贝,它们希望将状态与某一个线程
(例如,用户 ID 或事务 ID)相关联

我们知道有时候一个对象的变量会被多个线程所访问,这时就会有线程安全问题,当然我们可以使用synchorinized 关键字来为此变量加锁,进行同步处理,
从而限制只能有一个线程来使用此变量,但是加锁会大大影响程序执行效率,此外我们还可以使用ThreadLocal来解决对某一个变量的访问冲突问题。

当使用ThreadLocal维护变量的时候
 为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本,
 这样就不存在线程安全问题,也不会影响程序的执行性能。

但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,
比如内存的占用会比不使用ThreadLocal要大。

ThreadLocal 方法使用详解

ThreadLocal 的几个方法: ThreadLocal 可以存储任何类型的变量对象, get返回的是一个Object对象,
但是我们可以通过泛型来制定存储对象的类型。


public T get() { } // 用来获取ThreadLocal在当前线程中保存的变量副本

public void set(T value) { } //set()用来设置当前线程中变量的副本

public void remove() { } //remove()用来移除当前线程中变量的副本

protected T initialValue() { } //initialValue()是一个protected方法,一般是用来在使用时进行重写的

Thread 在内部是通过ThreadLocalMap来维护ThreadLocal变量表,
在Thread类中有一个threadLocals 变量,是ThreadLocalMap类型的,它就是为每一个线程来存储自身的ThreadLocal变量的,
ThreadLocalMap是ThreadLocal类的一个内部类,这个Map里面的最小的存储单位是一个Entry, 它使用ThreadLocal作为key,
变量作为 value,这是因为在每一个线程里面,可能存在着多个ThreadLocal变量

初始时,在Thread里面,threadLocals为空,当
通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找
 

SimpleDateFormat这个类是一个线程不安全的类在我们的项目日期工具类中都要用到

测试SimpleDateFormat的线程安全性
public class SimpleDateFormatTest {
    public static SimpleDateFormat sd= new SimpleDateFormat("yyyy-MM-dd");
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i <100; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Date parse = sd.parse("2018-03-01");
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println(parse);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

java.lang.NumberFormatException: For input string: ""
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Long.parseLong(Long.java:601)

这里有时候回报这个错  
解决这个问题有三种方案;
第一种:每次请求都创建一个新的DateFormat  这个对象比较大每次都区创建会消耗内存
第二种:加锁保证线程安全,用锁的话会影响程序的性能
第三种:ThreadLocal保存在本地的线程,如果本地已经有这个DateFormat 就不需要再次创建,没有的话再去
加锁 创建.这样大大提高了它的的速度.

public class SimpleDateFormatTest {
    private static final Lock lock = new ReentrantLock();
    private static ThreadLocal<Map<String, DateFormat>> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i <100 ; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    DateFormat simpleDateFormat = getSimpleDateFormat("yyyy-MM-dd");
                    try {
                        System.out.println(simpleDateFormat.parse("2018-01-01"));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    public static DateFormat getSimpleDateFormat(String parrent) {
        if (null == parrent || "" == parrent) {
            return null;
        }
        Map<String, DateFormat> stringDateFormatMap = threadLocal.get();
        if (null != stringDateFormatMap && stringDateFormatMap.containsKey(parrent)) {
            return stringDateFormatMap.get(parrent);
        }
        if (null == stringDateFormatMap) {
            stringDateFormatMap = new HashMap<>();
        }
        lock.lock();
        try {
            stringDateFormatMap.put(parrent, new SimpleDateFormat(parrent));
        } finally {
            lock.unlock();
        }
        threadLocal.set(stringDateFormatMap);
        return stringDateFormatMap.get(parrent);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值