1. 程式人生 > 其它 >學會這5種JS函式繼承方式,前端面試你至少成功50%

學會這5種JS函式繼承方式,前端面試你至少成功50%

關於

執行緒池為學習 JUC 的第二章,持續更新,直到完結 。如果您感興趣,可以前往本人的 JUC 標籤中。如果您有問題或者本人這裡寫的不對、不足,歡迎評論留言,感謝!!

第二章 ThreadLocal

1、兩大使用場景

小夥伴們看完 兩大使用場景 後或許有些疑惑,請閱讀下面的“ 3 、重要方法”內容,可能會對您有所幫助。

1、執行緒需要一個獨享的物件(例如工具類,典型需要使用的類有 SimpleDateFormat 和 Random)。

  1)併發使用靜態工具類是有很大風險的,此時可以使用 ThreadLocal 為每個執行緒都製作一個獨享的物件;

  2)使用執行緒池加上 ThreadLocal 可以節省資源,不需要建立更多的物件浪費資源。

示例:

public class ThreadLocalNormal {
    public static ExecutorService service = Executors.newFixedThreadPool(5);

    public String dateFormat(int seconds){
     //每個執行緒只會觸發一次下面的 initialValue() SimpleDateFormat format
= ThreadSafeFormatter.threadLocal.get(); return format.format(new
Date(seconds* 1000)); } public static void main(String[] args) { //使用此方法就不需要建立 1000 個 SimpleDateFormat 物件了,有多少個執行緒,就建立多少個 SimpleDateFormat 就夠了 for (int i = 0; i < 1000; i++) { int finalI = i; service.submit(() -> { System.out.println(new ThreadLocalNormal().dateFormat(finalI)); });
try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } service.shutdown(); } } class ThreadSafeFormatter { public static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){ @Override //這個函式是初始化作用 protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yy-mm-dd hh:mm:ss"); } }; }

2、執行緒內需要儲存全域性變數(例如在攔截器中獲取使用者資訊),可以讓不同方法直接使用,避免參數傳遞的麻煩。

  1)例如用 ThreadLocal 儲存一些業務內容(使用者許可權資訊、從使用者系統獲取到的使用者名稱、user ID 等),就不需要把一個資訊從 service1 傳遞到 service2 在傳遞 service3 .......

  2)每個執行緒使用 ThreadLocal 儲存的內容是不共享的,自己獨有的。

示例1:

/**
 * 避免傳遞引數的麻煩
 */
public class ThreadLocalNormal3 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 2; i++) {
            int j = i;
            executorService.execute(() -> {
                Service1 service1 = new Service1();
                service1.process("張三"+j,"test"+j);
            });
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        executorService.shutdown();
    }
}
class Service1 {
    public void process(String name,String password){
        User user = new User(name,password);
        UserContextHolder.holder.set(user);
        new Service2();
    }
}

class Service2 {
    public Service2(){
        System.out.println(Thread.currentThread().getName()+"---我是Service2:" + UserContextHolder.holder.get());
        new Service3();
    }
}

class Service3 {
    public Service3(){
        System.out.println(Thread.currentThread().getName()+"---我是Service3:" + UserContextHolder.holder.get());
    }
}
class UserContextHolder {
    public static ThreadLocal<User> holder = new ThreadLocal<>();

}
class User{
    public String name;
    public String password;
    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

執行結果:

示例二:

public class ThreadLocalNormal3 {
    public static ThreadLocal local = new ThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        local.set("我是主執行緒");
        new Thread(() -> {
            local.set("我是執行緒一");
            System.out.println(local.get());
        },"執行緒一").start();
        Thread.sleep(1000);
        System.out.println(local.get());
    }
}

執行結果:

2、使用 ThreadLocal 的好處

1、達到執行緒安全

  1)例如上面的場景一,我們可以把共享的靜態物件變成每個執行緒自己獨享的物件。

2、不需要加鎖,提高執行效率

  1)既然是每個執行緒自己所獨享的資源,執行緒就是安全的,不需要加鎖。

3、更高效地利用記憶體、節省開銷

  1)如上面場景一程式碼以及內容所示。

4、免去傳參的繁瑣:無論是場景一的工具類,還是場景二的使用者資訊,都可以在任何地方直接通過 ThreadLocal 拿到,再也不需要每次都傳遞同樣的引數。ThreadLocal 使得程式碼耦合度耕地,更優雅

3、重要方法

1、搞清楚 Thread、ThreadLocal、ThreadLocalMap 三者之間的關係:

如上圖所示:

  1)每個 Thread 物件中持有一個 ThreadLocalMap 成員變數;

  2)TreadLocalMap 是以 ThreadLocal 為 key 以獨享的資源為 value;

  3)因為一個 Thread 可以有多個不同的獨享資源 ,所以一個 ThreadLocalMap 中是以ThreadLocal 為 key 來標識每一個資源;

  4)線上程中,可以使用 ThreadLocal.get() 來獲取相應的資源。

2、T initialValue() :初始化

  1)該方法會返回當前執行緒對應的“初始值”(如果沒有重寫該方法,則初始值為 null),這是一個延遲載入的方法,只有在第一次呼叫 get 的時候,才會觸發(除非.......在 3)敘述.....)

  2)當前執行緒第一次使用 get 方法時,就會呼叫此方法,除非執行緒先前呼叫了 set 方法,在這種情況下,就不會呼叫該執行緒所對應自己的 initialValue 方法;

以上兩點正是對應了以上寫的兩種場景示例程式碼

  3)通常,每個執行緒最多呼叫一次此方法,但如果已經呼叫了 remove() 後,在呼叫 get() ,則可以再次呼叫此方法;

  4)如果不重寫本方法,這個方法會返回 null。一般使用匿名內部類的方法來重寫 initialValue() ,以便在後續使用中可以初始化副本物件。

3、void set(T t) :為這個執行緒設定一個新值,與setinitialValue() 很類似

原始碼:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //如果 map 不為空(該 map 可以儲存多個不同的 ThreadLocal),則更改目標 ThreadLocal 所對應的 value(this 為當前的 ThreadLocal)
        //注意:這個 map 以及 map 中的 key 和 value 都是儲存線上程中的
        if (map != null)
            map.set(this, value);
            //如果為空,則存入
        else
            createMap(t, value);
    }

4、T get() :得到ThreadLocalMap 中對應的 ThreadLocal 的值 。如果是首次呼叫 get(),則會呼叫 initialize 來得到這個值

  1)get 方法是先取出當前執行緒的 ThreadLocalMap,然後呼叫 map.getEntry 方法,把本 ThreadLocal 的引用作為引數傳入,取出 map 中屬於本 ThreadLocal 的 value。

原始碼:

5、void remove() :刪除ThreadLocalMap 裡面所對應的 key 以及 value(下面程式碼中,刪除所對應的 key 為 local 物件)

  1)remove() 在上面沒有做演示,在這裡簡單的做兩個示例:

示例一:

public class ThreadLocalNormal3 {
    public static ThreadLocal local = new ThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        IsNull();
    }
    
    public static void IsNull() throws InterruptedException {
        local.set("我是主執行緒");
        local.remove();
        Thread.sleep(1000);
        System.out.println("IsNull 方法結果:"+local.get());
    }
}

示例二:

public class ThreadLocalNormal3 {
    public static ThreadLocal local = new ThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        IsNull();
    }

    public static void IsNull() throws InterruptedException {
        local.set("我是主執行緒");
        local.remove();
        local.set("重新 set");
        Thread.sleep(1000);
        System.out.println("IsNull 方法結果:"+local.get());
    }
}

原始碼:

6、兩種場景殊途同歸

  1. setInitialValue 和直接 set 最後都是利用 map.set() 來設定;
  2. 也就是說,最後都會對應到 ThreadLocalMap 的一個 Entry,只不過是起點和入口不一樣

7、演示:一個執行緒,一個 ThreadLocalMap,兩個 ThreadLocal

public class ThreadLocalDemo {
    //一個主執行緒
    public static void main(String[] args) throws InterruptedException {
        //設定兩個ThreadLocal
        ThreadLocal local1 = new ThreadLocal();
        ThreadLocal local2 = new ThreadLocal();

        //分別為兩個 ThreadLocal 設定 value
        local1.set("我是 local1");
        local2.set("我是 local2");

        Thread.sleep(1000);

        //底層會自動的將這兩個 ThreadLocal 以及相應的 value 放入主執行緒的 ThreadLocalMap 中
        //輸出 ThreadLocalMap 中的這兩個 ThreadLocal 對應的 value
        System.out.println(local1.get()); //我是 local1
        System.out.println(local2.get()); //我是 local2
    }
}

4、ThreadLocalMap 類

1、ThreadLocalMap 類是每個執行緒 Thread 類裡面的變數,裡面最重要的是一個鍵值對陣列 Entry[] table。可以認為是一個 Map,鍵值對:

  1. 鍵:這個 ThreadLocal;
  2. 值:實際需要的成員變數,比如 user 或者 simpleDateFormat 物件。

2、ThreadLocalMap 這裡採用的是線性探測法,也就是說,如果發生衝突,就繼續找下一個空位置,而不是用連結串列拉鍊

5、其他

1、注意空指標異常,原理如程式碼中的註釋:

  在進行 get 之前,必須先 set,否則可能報空指標異常,一下程式碼報空指標異常

public class ThreadLocalNPE {
    ThreadLocal<Long> longThreadLocal = new ThreadLocal<>();
    public void set() {
        longThreadLocal.set(Thread.currentThread().getId());
    }
    //上面定義的是 ThreadLocal<Long>,這裡如果返回值是 long,那麼 get 到的值要做拆箱,如果拿 null 去拆箱,會報空指標異常
    public long get(){
        return longThreadLocal.get();
    }

    public static void main(String[] args) {
        ThreadLocalNPE threadLocalNPE = new ThreadLocalNPE();
            System.out.println(threadLocalNPE.get());
    }
}

2、共享物件問題

  1)如果在每個執行緒中 ThreadLocal.set() 進去的東西本來就是多執行緒共享的同一個物件,比如 static 物件,那麼多個執行緒的 ThreadLocal.get() 取得的還是這個共享物件本身,還是有併發訪問問題;

  2)如果可以不適用 ThreadLocal 就解決問題,那麼不要強行使用;

    • 例如在任務數很少的時候,在區域性變數中可以新建物件就可以解決問題,那麼就不需要使用到 ThreadLocal。

  3)優先使用框架的支援,而不是自己創造。

    • 例如在 Spring 中,如果可以使用 RequestContextHolder,那麼就不需要自己維護 ThreadLocal,因為自己可能會忘記呼叫 remove() 等,造成記憶體洩漏。