《Java 多執行緒程式設計核心技術》筆記——第3章 執行緒間通訊(四)

bm1998發表於2020-12-13

宣告:

本部落格是本人在學習《Java 多執行緒程式設計核心技術》後整理的筆記,旨在方便複習和回顧,並非用作商業用途。

本部落格已標明出處,如有侵權請告知,馬上刪除。

3.3 類 ThreadLocal 的使用

變數值的共享可以使用 public static 變數的形式,所有的執行緒都使用同一個 public static 變數。如果想實現每一個執行緒都有自己的共享變數該如何解決呢?

JDK 中提供的類 ThreadLocal 正是為了解決這樣的問題。

類 ThreadLocal 主要解決的就是每個執行緒繫結自己的值,可以將 ThreadLocal 類比喻成全域性存放資料的盒子,盒子中可以儲存每個執行緒的私有資料

3.3.1 方法 get() 與 null

建立測試類如下:

public class Run {
    public static ThreadLocal tl = new ThreadLocal();

    public static void main(String[] args) {
        if (tl.get() == null) {
            System.out.println("從未放過值");
            tl.set("我的值");
        }
        System.out.println(tl.get());
        System.out.println(tl.get());
    }

}

執行結果:

從未放過值
我的值
我的值

從執行結果來看第一次呼叫 tl 物件的 get() 方法時返回的值是 null,通過呼叫 set() 方法賦值後順利取出值並列印到控制檯上。類 Threadlocal 解決的是變數在不同執行緒間的隔離性,也就是不同執行緒擁有自己的值,不同執行緒中的值是可以放入 Threadlocal 類中進行儲存的

3.3.2 驗證執行緒變數的隔離性

  1. 建立公共類

    public class Tools {
        public static ThreadLocal tl = new ThreadLocal();
    }
    
  2. 建立兩個自定義執行緒類

    public class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 100; i++) {
                    if (Tools.tl.get() == null) {
                        Tools.tl.set("ThreadA" + (i + 1));
                    } else {
                        System.out.println("ThreadA get Value=" + Tools.tl.get());
                    }
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public class ThreadB extends Thread {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 100; i++) {
                    if (Tools.tl.get() == null) {
                        Tools.tl.set("ThreadB" + (i + 1));
                    } else {
                        System.out.println("ThreadB get Value=" + Tools.tl.get());
                    }
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
  3. 建立測試類

    public class Run {
        public static void main(String[] args) {
            try {
                ThreadA a = new ThreadA();
                ThreadB b = new ThreadB();
                a.start();
                b.start();
    
                for (int i = 0; i < 100; i++) {
                    if (Tools.tl.get() == null) {
                        Tools.tl.set("Main" + (i + 1));
                    } else {
                        System.out.println("Main get Value=" + Tools.tl.get());
                    }
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    執行結果:

    ThreadB get Value=ThreadB1
    ThreadA get Value=ThreadA1
    Main get Value=Main1
    ThreadB get Value=ThreadB1
    Main get Value=Main1
    ThreadA get Value=ThreadA1
    ThreadB get Value=ThreadB1
    ThreadA get Value=ThreadA1
    Main get Value=Main1
    ThreadB get Value=ThreadB1
    Main get Value=Main1
    ...
    

分析:雖然 3 個執行緒都向 tl 物件中 set() 資料值,但每個執行緒還是能取出自己的資料。

在第一次呼叫 Threadlocal 類的 get() 方法返回值是 null,怎麼樣實現第一次呼叫 get() 不返回 null 呢?也就是具有預設值的效果

3.3.3 解決 get() 返回 null 問題

  1. 繼承 ThreadLocal 類產生 ThreadLocalExt.java 類

    public class ThreadLocalExt extends ThreadLocal {
        @Override
        protected Object initialValue() {
            return "我是預設值 第一次get不再為null";
        }
    }
    
  2. 測試類

    public class Run {
        public static ThreadLocalExt tl = new ThreadLocalExt();
    
        public static void main(String[] args) {
            if (tl.get() == null) {
                System.out.println("從未放過值");
                tl.set("我的值");
            }
            System.out.println(tl.get());
            System.out.println(tl.get());
        }
    
    }
    

    執行結果:

    我是預設值 第一次get不再為null
    我是預設值 第一次get不再為null
    

此案例僅僅證明 main 執行緒有自己的值,那其他執行緒是否會有自己的初始值呢?

3.3.4 再次驗證執行緒變數的隔離性

  1. 繼承 ThreadLocal 類產生 ThreadLocalExt.java 類

    public class ThreadLocalExt extends ThreadLocal {
        @Override
        protected Object initialValue() {
            return new Date().getTime();
        }
    }
    
  2. 建立工具類

    public class Tools {
        public static ThreadLocalExt tl = new ThreadLocalExt();
    }
    
  3. 建立自定義執行緒類

    public class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println("在ThreadA執行緒中取值=" + Tools.tl.get());
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
  4. 測試類

    public class Run {
        public static void main(String[] args) {
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println("       在Main執行緒中取值=" + Tools.tl.get());
                    Thread.sleep(100);
                }
                Thread.sleep(5000);
                ThreadA a = new ThreadA();
                a.start();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    執行結果:

           在Main執行緒中取值=1607801033539
           在Main執行緒中取值=1607801033539
           在Main執行緒中取值=1607801033539
           在Main執行緒中取值=1607801033539
           在Main執行緒中取值=1607801033539
           在Main執行緒中取值=1607801033539
           在Main執行緒中取值=1607801033539
           在Main執行緒中取值=1607801033539
           在Main執行緒中取值=1607801033539
           在Main執行緒中取值=1607801033539
    在ThreadA執行緒中取值=1607801039548
    在ThreadA執行緒中取值=1607801039548
    在ThreadA執行緒中取值=1607801039548
    在ThreadA執行緒中取值=1607801039548
    在ThreadA執行緒中取值=1607801039548
    在ThreadA執行緒中取值=1607801039548
    在ThreadA執行緒中取值=1607801039548
    在ThreadA執行緒中取值=1607801039548
    在ThreadA執行緒中取值=1607801039548
    在ThreadA執行緒中取值=1607801039548
    

子執行緒和父執行緒各有各自所擁有的值。

3.4 類 InheritableThreadLocal 的使用

使用類 InheritableThreadLocal 可以在子執行緒中取得父執行緒繼承下來的值。

3.4.1 值繼承

使用 InheritableThreadLocal 類可以讓子執行緒從父執行緒中取得值

示例如下:

  1. 繼承 InheritableThreadLocal 類產生 InheritableThreadLocalExt.java 類

    public class InheritableThreadLocalExt extends InheritableThreadLocal {
        @Override
        protected Object initialValue() {
            return new Date().getTime();
        }
    }
    
  2. 建立工具類

    public class Tools {
        public static InheritableThreadLocalExt tl = new InheritableThreadLocalExt();
    }
    
  3. 建立自定義執行緒類

    public class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println("在ThreadA執行緒中取值=" + Tools.tl.get());
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
  4. 測試類

    public class Run {
        public static void main(String[] args) {
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println("       在Main執行緒中取值=" + Tools.tl.get());
                    Thread.sleep(100);
                }
                Thread.sleep(5000);
                ThreadA a = new ThreadA();
                a.start();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    執行結果:

           在Main執行緒中取值=1607802334937
           在Main執行緒中取值=1607802334937
           在Main執行緒中取值=1607802334937
           在Main執行緒中取值=1607802334937
           在Main執行緒中取值=1607802334937
           在Main執行緒中取值=1607802334937
           在Main執行緒中取值=1607802334937
           在Main執行緒中取值=1607802334937
           在Main執行緒中取值=1607802334937
           在Main執行緒中取值=1607802334937
    在ThreadA執行緒中取值=1607802334937
    在ThreadA執行緒中取值=1607802334937
    在ThreadA執行緒中取值=1607802334937
    在ThreadA執行緒中取值=1607802334937
    在ThreadA執行緒中取值=1607802334937
    在ThreadA執行緒中取值=1607802334937
    在ThreadA執行緒中取值=1607802334937
    在ThreadA執行緒中取值=1607802334937
    在ThreadA執行緒中取值=1607802334937
    在ThreadA執行緒中取值=1607802334937
    
    

分析:值成功地從父執行緒繼承下來

3.4.2 值繼承再修改

如果在繼承的同時還可以對值進行進一步的處理那就更好了。

  1. 修改類 InheritableThreadLocalExt.java 類

    public class InheritableThreadLocalExt extends InheritableThreadLocal {
        @Override
        protected Object initialValue() {
            return new Date().getTime();
        }
    
        @Override
        protected Object childValue(Object parentValue) {
            return parentValue + " 我在子執行緒加的~!";
        }
    }
    
  2. 再次執行,結果如下

           在Main執行緒中取值=1607802735419
           在Main執行緒中取值=1607802735419
           在Main執行緒中取值=1607802735419
           在Main執行緒中取值=1607802735419
           在Main執行緒中取值=1607802735419
           在Main執行緒中取值=1607802735419
           在Main執行緒中取值=1607802735419
           在Main執行緒中取值=1607802735419
           在Main執行緒中取值=1607802735419
           在Main執行緒中取值=1607802735419
    在ThreadA執行緒中取值=1607802735419 我在子執行緒加的~!
    在ThreadA執行緒中取值=1607802735419 我在子執行緒加的~!
    在ThreadA執行緒中取值=1607802735419 我在子執行緒加的~!
    在ThreadA執行緒中取值=1607802735419 我在子執行緒加的~!
    在ThreadA執行緒中取值=1607802735419 我在子執行緒加的~!
    在ThreadA執行緒中取值=1607802735419 我在子執行緒加的~!
    在ThreadA執行緒中取值=1607802735419 我在子執行緒加的~!
    在ThreadA執行緒中取值=1607802735419 我在子執行緒加的~!
    在ThreadA執行緒中取值=1607802735419 我在子執行緒加的~!
    在ThreadA執行緒中取值=1607802735419 我在子執行緒加的~!
    

但在使用 InheritableThreadLocal 類需要注意一點的是,如果子執行緒在取得值的同時,主執行緒將 InheritableThreadLocal 中的值進行更改,那麼子執行緒取到的值還是舊值。

相關文章