天天看點

ThreadLocal使用場景1、應用場景2、場景1建立對象副本-具體代碼demo展現第四版本:--優化代碼3、場景2-全局變量場景4、源碼淺談5、總結

目錄

1、應用場景

2、場景1建立對象副本-具體代碼demo展現

第一版本-正常版本

第二版本-改進版

第三版本-引進ThreadLocal

第四版本:--優化代碼

3、場景2-全局變量場景

4、源碼淺談

4.1 ThreadLocal、ThreadLocalMap、Thread關系

5、總結

1、應用場景

  1. 儲存每個線程獨享的對象、為每個線程建立一個副本、每個副本隻為目前的線程服務
  2. 儲存每個線程中需要獨立儲存的資訊、針對每個線程類似于全局變量

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關系

ThreadLocal使用場景1、應用場景2、場景1建立對象副本-具體代碼demo展現第四版本:--優化代碼3、場景2-全局變量場景4、源碼淺談5、總結

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

切記:切記: 在使用完成之後,需要從記憶體中移除,防止記憶體洩漏

           

繼續閱讀