ThreadLocal使用场景
Posted learning_code_blog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal使用场景相关的知识,希望对你有一定的参考价值。
目录
4.1 ThreadLocal、ThreadLocalMap、Thread关系
1、应用场景
- 保存每个线程独享的对象、为每个线程创建一个副本、每个副本只为当前的线程服务
- 保存每个线程中需要独立保存的信息、针对每个线程类似于全局变量
2、场景1创建对象副本-具体代码demo体现
我们以一个比较常见的例子,SimpleDateFormat
第一版本-常规版本
- 好处:编码简单
- 劣势:内存中需要创建多余的对象
public class ThreadLocalDemo01
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
/**
* 当前不存存在线程抢夺的情况,因为创建了1000个线程
*
* @param args
*/
public static void main(String[] args)
for (int i = 0; i < 1000; i++)
int j = i;
new Thread(() ->
String date = new ThreadLocalDemo01().date(j);
System.out.println(j + "===>" + date);
).start();
public String date(int i)
Date date = new Date(1000 * i);
return simpleDateFormat.format(date);
第二版本-改进版
- 好处: 只创建了一个对象
- 劣势: 多线程需要等待处理
public class ThreadLocalDemo02
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
private static ExecutorService executorService = Executors.newFixedThreadPool(20);
/**
* 线程处理不安全,会出现资源抢夺情况
* static 修饰的资源在常量池中。共用同一个
* *
*
* @param args
*/
public static void main(String[] args)
for (int i = 0; i < 1000; i++)
int j = i;
executorService.submit(() ->
String date = new ThreadLocalDemo02().date(j);
System.out.println(j + "===>" + date);
);
executorService.shutdown();
public synchronized String date(int i)
Date date = new Date(1000 * i);
// 方式1 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss"); // 每次调用都会创建一个对象、创建了1000个对象调用
//方式2 添加Synchronize 关键字、但是有点得不偿失、其他线程都得等待
synchronized (ThreadLocalDemo02.class)
return simpleDateFormat.format(date);
/*** 共用同一个日期格式对象 ,出现线程不安全问题
* return simpleDateFormat.format(date);
*/
第三版本-引进ThreadLocal
- 好处:会根据线程数来创建对应的对象,节省内存
- 劣势:代码还可以优化一下
public class ThreadLocalDemo03
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
static Map map = new ConcurrentHashMap();
/**
* *
*
* @param args
*/
public static void main(String[] args)
for (int i = 0; i < 1000; i++)
int j = i;
executorService.submit(() ->
String date = new ThreadLocalDemo03().date(j);
System.out.println(j + "===>" + date);
);
executorService.shutdown();
TheadLocalHolder.clear();
public String date(int i)
Date date = new Date(1000 * i);
SimpleDateFormat simpleDateFormat = TheadLocalHolder.dateFormatThreadLocal.get();
// 验证是否同一个对象(开多少个线程就会有多少个对象)
if (map.containsKey(simpleDateFormat))
System.out.println("命中一次");
else
map.put(simpleDateFormat, 1);
System.out.println("创建dateFormat对象");
// System.out.println(System.identityHashCode(simpleDateFormat));
return simpleDateFormat.format(date);
class TheadLocalHolder
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss"));
public static void clear()
dateFormatThreadLocal.remove();
第四版本:--优化代码
- 改进第三版本的复杂
public class ThreadLocalDemo04
/**
* 生产中禁用这种创建多线程的方式,应该采用线程池构建函数的方式创建
*/
static ExecutorService executorService = Executors.newFixedThreadPool(7);
static Map map = new ConcurrentHashMap();
public static void main(String[] args)
for (int i = 0; i < 1000; i++)
int millSecond = i;
executorService.submit(() ->
SimpleDateFormat simpleDateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss")).get();
Date date = new Date(millSecond * 1000);
if (map.containsKey(simpleDateFormat))
System.out.println("命中一次");
else
map.put(simpleDateFormat, 1);
System.out.println("创建dateFormat对象");
String date2 = simpleDateFormat.format(date);
// String date = new ThreadLocalDemo04().date(millSecond);
System.out.println(millSecond + "==" + date2);
);
executorService.shutdown();
3、场景2-全局变量场景
我们在前面过滤器中可以放入对应的参数,在后面的需要用到时就不用每个参数透传了
比如: Person对象,只是部分处理器需要,还是需要每个方法都需要透传 ===> 我们就可以引入 TheadLocal来进行存储
public class ThreadLocalContext
public static void main(String[] args)
Filter filter = new Filter();
filter.process();
// 需要调用 remove方法,防止内存泄漏
UserHoldContext.userContext.remove();
UserHoldContext.personContext.remove();
class Filter
public void process()
User user = new User();
user.setName("this is yx do thing ,");
UserHoldContext.userContext.set(user);
System.out.println("process");
// 可以查看 ThreadLocal Thread ThreadLocalMap 对象之间的关系
Person person = new Person(1);
UserHoldContext.personContext.set(person);
Service1 service1 = new Service1();
service1.test1();
class Service1
public void test1()
User user = UserHoldContext.userContext.get();
System.out.println("test1"+user.getName());
Service2 service2 = new Service2();
service2.test2();
class Service2
public void test2()
Service3 service3 = new Service3();
User user = UserHoldContext.userContext.get();
System.out.println("test2"+user.getName());
service3.test3();
class Service3
public void test3()
User user = UserHoldContext.userContext.get();
System.out.println(" test3"+user.getName());
System.out.println("test3");
class UserHoldContext
public static ThreadLocal<User> userContext = ThreadLocal.withInitial(() ->
System.out.println("创建user对象");
return new User();
);
public static ThreadLocal<Person> personContext = new ThreadLocal<Person>();
class Person
private int age;
public Person()
public Person(int age)
this.age = age;
class User
private String name;
public User(String name)
this.name = name;
public String getName()
return name;
public void setName(String name)
this.name = name;
public User()
4、源码浅谈
4.1 ThreadLocal、ThreadLocalMap、Thread关系
5、总结
# ThreadLocal的应用场景-有两个
- 场景1:保存每个线程独享的对象,为每个线程创建一个副本,这样每个线程可以修改自己所拥有的副本了
- 场景2:每个线程内的需要独立保存信息,以便其他方法可以方便的获取
场景1:
典型的用法就是 SimpleDateFormat 对象
在多线程中,没有必要每次都创建一个新的SimpleDateFormat,只需要为每个线程分配一个即可。可以引入threadLocal创建副本对象
`com.yx.test.threadlocal.part1.ThreadLocalDemo04 ` 可以查看实现
场景2:
保存每个线程分配的对象、类似于全局对象一样。比如我们在拦截器里面获取的userId,userType
在后面的方法中也是需要调用,一般的处理方式,是直接透传、封装成一个map参数传递
其实这时候我们可以采用threadLocal进行透传,demo 如下
com.yx.test.threadlocal.part2.ThreadLocalContext
# Thread、ThreadLocalMap、ThreadLocal
每个Thread中都有一个ThreadLocalMap对象,一个ThreadLocalMap对象中有多个ThreadLocal,
key: ThreadLocal value 为存储的对象, 比如 User、Option
切记:切记: 在使用完成之后,需要从内存中移除,防止内存泄漏
以上是关于ThreadLocal使用场景的主要内容,如果未能解决你的问题,请参考以下文章