首页 >> 大全

JUC之ThreadLocal

2024-01-02 大全 20 作者:考证青年

若有不对之处欢迎大家指出,这个也是在学习工作中的一些总结,侵删!

得之在俄顷,积之在平日。

1、使用场景

每个线程需要独享的对象(通常是工具类,典型需要使用的类有和),每个内有自己的实例副本,不共享。

每个线程内需要保存全局变量(例如在拦截器中获取用户信息),可以在不同的地方直接使用,避免参数传递的麻烦,例如:当前用户信息需要被线程内的所有方法共享,一个比较繁琐的解决方案是把user作为参数层层传递,从一个传递到另一个,以此类推,但是这样会导致代码冗余且不易维护;解决方法:如果用保存一些业务内容(用户权限、信息,从用户系统获取到的用户名、等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不同的,在线程的生命周期内,都通过这个静态的实例的get()方法取得自己set过的那个对象,避免了将这个对象(例如:User对象)作为参数传递的麻烦。强调的是同一个请求内(同一个线程内)不同方法的共享。

2、的两个作用:

让某个需要用到的对象在线程间隔离(每个线程都有自己独立的对象)

在任何方法中都可以轻松的获取到该对象

3、示例

注:采用java8新日期不会出现线程安全问题

//打印1000个日期  用10个线程来执行
//创建线程池
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
//工具类,进行日期转换
public static String DateTransition(long seconds) {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-DD HH:mm:ss");Date date = new Date(1000*seconds);return dateFormat.format(date);
}
public static void main(String[] args) {for (int i = 0; i < 1000; i++) {//i的十倍作为毫秒数long scond = i*10;executorService.submit(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubString date = new ThreadLocalTest01().DateTransition(scond);System.out.println("--->"+date);}});}executorService.shutdown();
}

这样做看似没什么问题,实际上当执行1000次的时候则会创建1000个,如下图:

然后进行再次升级,将提取出来作为静态常量,所有线程共享这一个,就会发生线程安全问题

static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-DD HH:mm:ss");
//工具类,进行日期转换
public  String DateTransition(long seconds) {Date date = new Date(1000*seconds);return dateFormat.format(date);
}

在这里我们可以选择加锁来解决线程安全问题。

//工具类,进行日期转换
public  String DateTransition(long seconds) {Date date = new Date(1000*seconds);String endDate = null;synchronized (ThreadLocalTest01.class){endDate = dateFormat.format(date);}return endDate;
}

加锁能解决问题,但是会有性能开销,如果使用就不会存在这个问题

JUC之ThreadLocal_JUC之ThreadLocal_

//打印1000个日期  用10个线程来执行
//创建线程池
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
//static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-DD HH:mm:ss");
static ThreadLocal<SimpleDateFormat> threadLocaldateFormat = new ThreadLocal<SimpleDateFormat>(){@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-DD HH:mm:ss");}
};
//工具类,进行日期转换
public  String DateTransition(long seconds) {Date date = new Date(1000*seconds);SimpleDateFormat simpleDateFormat = threadLocaldateFormat.get();return simpleDateFormat.format(date);
}
public static void main(String[] args) {for (int i = 0; i < 1000; i++) {//i的十倍作为毫秒数long scond = i*10;executorService.submit(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubString date = new ThreadLocalTest01().DateTransition(scond);System.out.println("--->"+date);}});}executorService.shutdown();
}

一个请求,调用多个业务,这样就会将这个请求所带的数据进行一层一层的传递,会导致代码冗余且不易维护

解决方案:采用将参数放在一个map里面,然后各层都能取到map里面的数据,这样每个线程里面的map都不一样

public class ThreadLocalTest02 {//一个请求,调用多个业务public static void main(String[] args) {UserRequest UserRequest = new UserRequest();UserRequest.request("123");}}class CreateThreadLocal{public static ThreadLocal<String> threadLocal = new ThreadLocal<>();}class UserRequest{void request(String id){//请求CreateThreadLocal.threadLocal.set(id);UserService01 userService01 = new UserService01();userService01.Service01();}}class UserService01{void Service01(){//业务一System.out.println("Service01拿到数据:"+CreateThreadLocal.threadLocal.get());UserService02 userService02 = new UserService02();userService02.Service02();}}class UserService02{void Service02(){//业务二System.out.println("Service02拿到数据:"+CreateThreadLocal.threadLocal.get());UserService03 userService03 = new UserService03();userService03.Service03();}}class UserService03{void Service03(){//业务三System.out.println("Service03拿到数据:"+CreateThreadLocal.threadLocal.get());CreateThreadLocal.threadLocal.remove();}}

4、使用场景:

在ThreadLocal第一次get的时候把对象初始化出来,对象的初始化时机可以由我们控制(常用于工具类)

5、set的使用场景

如果需要保存到里面的对象的生成时机不由我们随意控制,例如拦截器生成的用户信息,用.set直接放到我们的中去,以便后续使用。

6、使用带来的好处

达到线程安全。

不需要加锁,提高执行效率。

更高的利用内存,节省开销:相比于每个任务都新建一个,显然用可以节省内存和开销。

免去传参的繁琐,使得代码耦合度低,更优雅。

7、解析

类:

也就是ThreadLocals, ThreadLocalMap类是每个线程Thread里面的变量,里面最重要的是一个键值对数组Entry[] table,可以认为是一个map,键值对:
键:这个ThreadLocal
值:实际需要的成员变量,比如我们放进去的id
ThreadLocalMap采用的是线性探测法,也就是如果发生冲突,就继续找下一个位置,而不是使用链表拉链,不想map那样采用链表和红黑树

8、主要方法介绍:

T ():初始化 。

【该方法会返回当前线程对应的“初始值”,这是一个延迟加载的方法,只有在调用get的时候才会触发,当线程第一次使用get方法访问变量时调用此方法,除非线程先前调用了set方法,这种情况下,不会为线程调用本方法;通常,每个线程最多调用一次此方法,但如果调用了()后再调用get(),则可再次调用此方法;如果不重写本方法,这个方法会返回null,一般使用匿名内部类的方法来重写()方法,以便在后续使用过程中可以初始化副本对象。】

void set(T t):为这个线程设置新值。

T get():得到这个线程对应的value,如果是首次调用get,则会调用来得到这个值。

void ():删除对应这个线程的值。

9、注意点:

内存泄漏

内存泄漏是指某个对象不再有用,但是占有的内存却不能被回收

		key的泄漏:ThreadLocalMap中的Entry继承自	weakReference,是弱引用(弱应用的特点是,如果这个对象只被弱应用关	联,没有任何强引用关联,那么这个对象就可以被回收,所以弱引用不会	阻止GC)![在这里插入图片描述](https://img-blog.csdnimg.cn/20200223135951481.png)Value泄漏:ThreadLocalMap的每个Entry都是一个对key的	弱引用,同时每个Entry都包含了一个对Value的强引用,正常情况下,	当线程终止,保存在ThreadLocal里面的Value会被垃圾回收,因为没有	任何强引用了,但是如果线程不终止或者持续很长时间,那么key对应的	Value就不能被回收,因为有以下调用链:
Thread->ThreadLocalMap->Entry(key为null)->value
因为Value和Thread之间还存在这个强引用链路,所以会导致Value无法	被回收,就可能出现OOM。JDK已经考虑到了这个问题,所以在	set,remove,rehash方法中会扫描key为null的Entry,并把对应的value	值设置为null,这样对象就可以被回收,但是如果不调用这些方法,那么	就会导致了Value的内存泄漏。

如何避免内存泄漏

	调用remove()方法就会删除对应的Entry对象,可以避免内存泄漏,所以在使用完ThreadLocal之后应该调用remove()方法

空指针异常

基本数据类型《==》包装类

共享对象

不能将static放入ThreadLocal中

优先使用框架支持

例如:在Spring中,如果可以使用RequestContextHolder,那么就不需要自己维护ThreadLocal,因为自己可能会忘记调用remove等方法造成内存泄漏。

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了