目錄
1、應用場景
2、場景1建立對象副本-具體代碼demo展現
第一版本-正常版本
第二版本-改進版
第三版本-引進ThreadLocal
第四版本:--優化代碼
3、場景2-全局變量場景
4、源碼淺談
4.1 ThreadLocal、ThreadLocalMap、Thread關系
5、總結
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、總結
demo 代碼
# 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
切記:切記: 在使用完成之後,需要從記憶體中移除,防止記憶體洩漏