关于概念和使用就不多介绍了,自行百度
这里主要针对许多博客都没解释清楚的点进行说明,也是我曾疑惑的点。
(1)在每个线程中创建一个对象和在每次任务执行时创建对象的区别?
(2)在继承ThreadLocal对象重写initialValue方法时是否需要重新new 对象,此时的new 对象又是什么意思呢?
先上代码:
//未使用ThreadLocal以及锁,而是每个线程在执行任务时都会去新建一个对象
public class HHHH {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
for (int i = 1; i <=100; i++) {
int finalI = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(new HHHH().date(finalI));
}
});
}
}
public String date(int seconds){
Date date = new Date(1000*seconds);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd:hh:mm:ss");
return simpleDateFormat.format(date);
}
}
//使用ThreadLocal,每个线程存放一个ThreadLocal对象
public class HHHH {
public static ExecutorService executorService= Executors.newFixedThreadPool(4);
public static void main(String[] args) {
for (int i = 1; i <=1000; i++) {
int finalI = i;
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(new HHHH().date(finalI));
}
});
}
executorService.shutdown();
}
public String date(int seconds){
Date date = new Date(1000*seconds);
SimpleDateFormat simpleDateFormat = ThreadSafe.dateFormatThreadLocal.get();
return simpleDateFormat.format(date);
}
}
class ThreadSafe {
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal= new ThreadLocal<SimpleDateFormat>(){
SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd:hh:mm:ss");
//若执行get方法时,没有赋值,此时会执行该方法,返回该方法的值,并且会在当前线程的ThreadLocalMap中存放值是该value的键值对。
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd:hh:mm:ss");//每个线程返回一个对象,不会出现线程安全问题
// return sim ; //此时才是不占内存的,因为在每个线程的threadLocalMap中存放的都是该对象的引用,但是你会发现还是出现了线程安全问题,因为我们操作的是一个对象嘛
}
};
}
仔细阅读代码,你会有很多发现。
上结论:
(1)在方法中创建对象,则每执行一次方法都会创建一个对象,比如代码中写了1000次执行方法,则创建了1000SimpleDateFormat个对象,不存在线程安全问题,但极大的占用堆内存空间;
(2)使用ThreadLocal,在每个线程中创建一个对象,其实主要是利用到了线程池的线程复用,即一个线程会执行n个任务,比如代码中只创建了一个有4个线程的线程池,那么这4个线程其实创建了4个SimpleDateFormat对象,而线程与线程之间的对象不同保证了线程安全,但又减少了占用的内存空间
(3)在实现initialValue方法时,创建新对象,是返回给每一个线程使用的;若选择返回对象引用,那么所有的线程操作的都是同一个对象,仍然存在线程安全问题。
(4)补充一下initialValue方法的逻辑,方便更容易理解上一点。在执行get方法时,会去判断当前线程中是否有threadlocalMap对象,若有则会取值返回,若没有,则会执行setInitialValue方法,该方法会去执行initialValue方法获取值,并且!会将该键值对存到Map中。那么也就是,每一个线程肯定只会执行一次setInitialValue方法,即只会执行一次initialValue方法。
综上所述:使用ThreadLocal避免内存的消耗,其实还是得益于线程复用。
欢迎留言讨论哦~期待你的关注