ThreadLocal
使用场景:
- 保存线程不安全的工具类。典型 SimpleDateFormat
package com.threadLocal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.lang.ThreadLocal;
/**
* 16个线程对应16个simpleDateFormat对象
* 每个线程都有自己的副本,是线程安全的
*/
public class ThreadLocalDemo06 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(16);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.submit(new Runnable() {
@Override
public void run() {
String date = new ThreadLocalDemo06().date(finalI);
System.out.println(date);
}
});
}
threadPool.shutdown();
}
public String date(int seconds) {
Date date = new Date(1000 * seconds);
SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
return dateFormat.format(date);
}
}
class ThreadSafeFormatter {
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("mm:ss");
}
};
}
每个Thread 内部都有自己的实例副本,且该副本只能由当前 Thread 访问到并使用,相当于每个线程内部的本地变量
- ThreadLocal 用作每个线程内需要独立保存信息的场景,供其他方法更方便地获取信息。
每个获取到的信息可能呢是不一样的,前面执行的方法设置了信息后,后续方法可以通过ThreadLocal直接获取到,避免了传参。
package com.threadLocal;
import java.lang.ThreadLocal;
public class ThreadLocalDemo07 {
public static void main(String[] args) {
new Service1().service1();
}
}
class Service1 {
public void service1() {
User user = new User("拉勾教育");
UserContextHolder.holder.set(user);
new Service2().service2();
}
}
class Service2 {
public void service2() {
User user = UserContextHolder.holder.get();
System.out.println("Service2拿到用户名:" + user.name);
new Service3().service3();
}
}
class Service3 {
public void service3() {
User user = UserContextHolder.holder.get();
System.out.println("Service3拿到用户名:" + user.name);
UserContextHolder.holder.remove();
}
}
class UserContextHolder {
public static ThreadLocal<User> holder = new ThreadLocal<>();
}
class User {
String name;
public User(String name) {
this.name = name;
}
}
ThreadLocal 不是用来解决共享资源的多线程访问问题的
- 在 initalValue 中 new 出自己的线程独享的资源,多个线程质监局,他们访问的对象本省是不共享的,不存在任何并发问题。
这是ThreadLocal解决并发问题的主要思路。
package com.threadLocal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.lang.ThreadLocal;
public class ThreadLocalStatic {
public static ExecutorService threadPool = Executors.newFixedThreadPool(16);
static SimpleDateFormat dateFormat = new SimpleDateFormat("mm:ss");
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.submit(new Runnable() {
@Override
public void run() {
String date = new ThreadLocalStatic().date(finalI);
System.out.println(date);
}
});
}
threadPool.shutdown();
}
public String date(int seconds) {
Date date = new Date(1000 * seconds);
SimpleDateFormat dateFormat = ThreadSafeFormatter1.dateFormatThreadLocal.get();
return dateFormat.format(date);
}
}
class ThreadSafeFormatter1 {
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return ThreadLocalStatic.dateFormat;
}
};
}
-
ThreadLocal 是通过让每个线程独享自己的副本,避免了资源的竞争
-
synchronized主要用于临界资源的分配,在同一时刻限制最多的只有一个线程能访问该资源
多个ThreadLocal在Thread中的threadLocals里是怎么存储的
-
每个Thread对象中都持有一个ThreadLocalMap类型的成员变量
-
key是ThreadLocal的引用
-
一个Thread里只有一个ThreadLocalMap
-
一个ThreadLocalMap里有很多的ThreadLocal
内存泄露:为何每次用完ThreadLocal都要调用 remove?
- 内存泄露
某一个对象不再有用的时候,占用的内存却不能被回收
2.每个线程可以通过 ThreadLocal 来存取自己线程专属的一个变量副本, ThreadLocalMap 中的 Enry 继承了 WeakReference(这个对象不会阻止GC)。
-
每个Entry都是对key的弱引用,但是这个Entry包含了一个对value的强引用
-
ThreadLocalMap key-value:key是WeakReference,value就是自己放的变量副本
-
如果线程长期存活,ThreadLocal里会一直有这个线程的 key-value。万一发现内存不够的情况进行了GC,
此时就会自动把很多线程在ThreadLocal里存放的key-value对的key,弱引用进行回收。 -
在通过ThreadLocal、set、get、remove、rehash,都会扫描key为null的entry。
他会自动清理掉map里值为null的key,确保不会有很多的null值引用了你的value造成内存泄露的问题,这个就是一个他自己的解决方案。 -
假设这个ThreadLocal已经不被使用了,那么实际上set、remove、rehash方法不会被调用。
与此同时,如果这个线程又一直存活、不终止的话,那么刚才的那个调用链就一直存在,就导致了value的内存泄露。 -
调用ThreadLocal的remoce方法:调用这个方法就可以删除对应的value对象,可以避免内存泄露
-
尽量避免在 ThreadLocal 长期放入数据,不使用时最好及时进行remove,自己主动把数据删除了。