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);
}
}