《Java後端知識體系》系列之併發編程基礎

想了想還是從基礎開始整理併發編程的知識吧,實在是太多了!!!

併發編程基礎

  • 基礎概念:

    • 線程與進程

      • 進程:進程是系統進行資源分配和調度的基本單位,平時我們在電腦上啓動的一個程序就是一個進程。
      • 線程:線程是操作系統進行調度的最小單位
      • 關係:一個進程可以啓動一個或多個線程,進程中所有的線程會共享進程中的內存空間,每個線程都有自己的程序計數器棧區域,進程中的棧資源用來存儲該線程的局部變量,這些局部變量是線程私有
      • 線程與進程的關係如圖所示:
        在這裏插入圖片描述
    • 線程的創建

      • Java中有三種線程創建方式,分別是實現Rannable接口的run方法、繼承Thread類重寫run方法、實現Callable接口

      • 繼承Thread
        繼承Thread類的實現代碼如下:

        	/**
        	 * @author admin
        	 */
        	public class ThreadTest {
        		//繼承Thread類並重寫run方法
        	    public static class MyThread extends Thread{
        	        @Override
        	        public void run(){
        	            System.out.println("a thread");
        	        }
        	    }
        	
        	    public static void main(String[] args) {
        	    	//創建線程
        	        MyThread myThread = new MyThread();
        	        //啓動線程
        	        myThread.start();
        	    }
        	}
        
        
        • 在以上代碼中MyThread繼承Thread類,並重寫了run方法。在main函數中創建MyThread實例,然後調用MyThread的start()方法啓動該線程。但是我們要知道在創建MyThread對象後線程並沒有立即啓動執行,需要調用start方法後才啓動線程。

        • 調用start方法之後線程並沒有馬上執行而是線程處於就緒狀態,等待獲取到CPU資源之後才真正處於運行狀態

        • run方法執行完畢,線程就處於終止狀態

        • 使用繼承Thread類的好處是,在run方法內獲取線程時直接調用this就可以了,無需通過Thread.currentThread()方法;不好的地方是Java不允許多繼承,如果繼承了Thread類就無法繼承其它類。並且任務與代碼沒有分離,當多個線程執行一樣的任務時需要多份任務代碼。

      • 實現Rannable

        實現Runnable接口代碼如下:

            public static class RunnableTask implements Runnable{
                @Override
                public void run() {
                    System.out.println("a runnable thread");
                }
            }
            public static void main(String[] args) {
                RunnableTask runnableTask = new RunnableTask();
                new Thread(runnableTask).start();
                new Thread(runnableTask).start();
            }
        
        • 在以上代碼中,兩個線程公用了一個runnableTask代碼邏輯,並且也可以根據需要給RunnableTask添加參數進行區分(重載)。另外RunnableTask可以繼承其它類。但是繼承Thread類和實現Runnable接口的方式都是沒有返回值的。
      • 實現Callable接口
        實現Callable接口方式:

        		//創建任務類
        public static class CallTask implements Callable<String>{
        
            @Override
            public String call() throws Exception {
                return "hello callAble";
            }
        }
        
        public static void main(String[] args) throws InterruptedException {
            //創建異步任務
            FutureTask<String> futureTask = new FutureTask<>(new CallTask());
            //啓動線程
            new Thread(futureTask).start();
            try {
                //等待任務執行結束並返回結果
                String result = futureTask.get();
                System.out.println(result);
            }catch (ExecutionException e){
                e.printStackTrace();
            }
        }
        
        • 在以上代碼中實現Callable接口的call()方法,在main函數中創建一個FutureTask對象(構造函數爲CallTask的實例),然後使用創建的FutureTask對象作爲任務創建一個線程並啓動它,最後通過futureTask.get()等待任務執行完畢並返回結果。
      • 總結:

        • 使用繼承Thread類的好處是方便傳參數,可以在子類中添加成員變量,通過set方法設置參數或者構造函數進行傳遞;如果使用實現Runnable接口的方式,則只能使用主線程裏面被聲明爲final的變量,但是前兩種都沒有返回結果,但是Callable接口可以實現獲取返回結果。
    • 線程等待和通知

      • Object級別

        • wait:當一個線程調用共享變量的wait()方法時,該調用線程會被阻塞掛起

          • 直到發生一下幾件事才返回:(1)其它線程調用該共享對象的notify()或者notifyAll()方法,(2)其它線程調用了該線程的interrupt()方法,該線程拋出InterruptedException異常返回。

          • 另外,如果調用wait()方法的線程沒有事先獲取對象的監視器鎖,則調用wait()方法時調用線程會拋出IllegalMonitorStateException異常。一個線程獲取該共享變量的監視器鎖的方式有兩種:1、執行synchronized同步代碼塊,使用該共享變量作爲參數

            synchronized(共享變量){
            	//doSomething
            }
            

            2、調用該共享變量的方法,並且該方法使用了synchronized修飾

            synchronized void add(int a,int b){
            	//doSomething
            }
            
          • 另外需要注意的是,一個線程可以從掛起狀態變爲可以運行狀態,即使該線程沒有被其它線程調用notify()、notifyAll()方法進行喚醒,或者被中斷等,這就是所謂的虛假喚醒。雖然虛假喚醒很少發生,但是要防患於未然,做法就是不停的測試該線程被喚醒的條件是否滿足,不滿足就繼續等待,也就是說在一個循環調用中調用wait()方法進行防範。退出的條件是滿足了喚醒該線程的條件。

            synchronized(obj){
            	while(條件不滿足){
            		obj.wait();
            		}
            }
            
          • 線程掛起的方法也存在一個wait(long timeout)的方法,如果一個線程調用共享對象的wait(long timeout)方法後,如果沒有在指定的timeout ms時間內被其它線程調用該共享變量的notify()和notifyAll()方法喚醒,那麼該方法會因爲超時而返回。如果將timeout設置爲0,則效果和wait()方法效果一樣。

        • notify

          • 一個線程調用共享對象的notify()方法後,會喚醒一個在共享變量上調用wait系列方法後被掛起的線程。一個共享變量上可能有多個線程在等待,具體喚醒哪個線程是隨機的。
          • 此外被喚醒的線程並不會馬上從wait方法返回並執行,它必須在獲取了共享對象的監視器鎖後纔可以返回,也就是喚醒它的線程釋放了共享變量的監視器鎖之後,被喚醒的線程也不一定會直接獲取到共享變量的監視器鎖,因爲該線程還需要和其它線程競爭該鎖(所以說synchronized鎖並不是公平的),只有競爭到共享變量的監視器鎖(synchronized鎖)之後纔可以繼續執行。
        • notifyAll

          • 不同於notify()會喚醒被阻塞到該共享變量的一個線程,notifytAll()會喚醒所有的該共享變量上調用wait系列方法被掛起的線程。

            //創建共享變量
            private static volatile Object object = new Object();
            
            public static void main(String[] args) throws InterruptedException {
                //創建線程A
                Thread threadA = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (object) {
                            try {
                                System.out.println("threadA start wait");
                                object.wait();
                                System.out.println("threadA wait end");
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });
                //創建線程B
                Thread threadB = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (object) {
                            try {
                                System.out.println("threadB start wait");
                                object.wait();
                                System.out.println("threadB wait end");
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });
                //創建線程C
                Thread threadC = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (object) {
                            System.out.println("threadC start notifyAll");
                            object.notifyAll();
                            System.out.println("threadC notifyAll end");
                        }
                    }
                });
                threadA.start();
                threadB.start();
                Thread.sleep(1000);
                threadC.start();
                //等待線程結束
                threadA.join();
                threadB.join();
                threadC.join();
            }
            

            在這裏插入圖片描述
            如上代碼中我們創建兩個線程並都執行wait()方法掛起線程,然後沉睡一秒之後執行線程C執行notifyAll(),這樣線程A和線程B被同時喚醒,因此線程A和線程B需要競爭共享變量object的監視器鎖,誰先獲取到監視器鎖誰就先執行。如上的執行結果中我們可以看到線程A先獲取到了監視器鎖然後執行了後續的操作。

      • Thread級別

        • sleep:讓線程睡眠的方法

          • 當一個執行中的線程執行了sleep()方法後,該線程會暫時讓出CPU的執行權,不參與CPU的調度,但是該線程仍然持有監視器鎖(也就是不會釋放鎖)。當指定的睡眠時間到了之後該函數會正常返回,線程就處於就緒狀態,重新參與CPU調度,獲取到CPU資源之後就可以繼續運行。如果在睡眠期間其它線程調用了睡眠中的線程的interrupt()方法中斷該線程,那麼該線程會在調用sleep()的地方拋出IntermptedException異常並返回。

            public class SleepTest {
                //創建一個獨佔鎖
                private static final Lock lock = new ReentrantLock();
            
                public static void main(String[] args) throws InterruptedException {
                    //創建線程A
                    Thread threadA = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            //獲取獨佔鎖
                            lock.lock();
                            try {
                                System.out.println("threadA start sleep");
                                Thread.sleep(10000);
                                System.out.println("threadA end sleep");
                            }catch (InterruptedException e){
                                e.printStackTrace();
                            }finally {
                                //釋放鎖
                                lock.unlock();
                            }
                        }
                    });
                    //創建線程B
                    Thread threadB = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            //獲取獨佔鎖
                            lock.lock();
                            try {
                                System.out.println("threadB start sleep");
                                Thread.sleep(10000);
                                System.out.println("threadB end sleep");
                            }catch (InterruptedException e){
                                e.printStackTrace();
                            }finally {
                                //釋放鎖
                                lock.unlock();
                            }
                        }
                    });
                    //啓動線程
                    threadA.start();
                    threadB.start();
                }
            }
            

            在這裏插入圖片描述

          • 以上代碼中創建兩個線程,threadA和threadB,並且兩個線程都休眠10秒,並且啓動兩個線程,這樣的情況不論執行多少次都會是threadA先執行,因爲threadA睡眠時並不會釋放鎖,依然持有獨佔鎖資源,所以最後睡眠結束時依然是先持有鎖的線程先執行。

          • 另外如果在sleep(long millis)中millis參數傳遞了一個負數,則會拋出IllegalArgumentException異常。

        • join:等待線程執行終止的方法

          • 當多個線程加載資源時,需要等待所有的線程執行完畢之後再做彙總處理的情況時,就需要一個方法來控制這些線程的執行,控制所有線程執行完畢才釋放線程,此時就需要用到join()方法。
              public static void main(String[] args) throws InterruptedException {
                  Thread threadA = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          try {
                              Thread.sleep(1000);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println("threadA over");
          
                      }
                  });
                  Thread threadB = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          try {
                              Thread.sleep(1000);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println("threadB over");
                      }
                  });
                  //啓動子線程
                  threadA.start();
                  threadB.start();
                  //等待子線程執行完畢返回
                  threadA.join();
                  threadB.join();
              }
          
          • 在以上代碼中啓動了兩個線程,分別調用了join()方法,那麼當主線程執行到threadA.join()時會被阻塞,等待threadA執行完畢之後返回,因此主線程執行到join()方法時會被阻塞,等待threadA的線程執行成功後才能繼續執行。同理threadB也是如此,主線程執行到threadB時也會被阻塞,等待threadB執行成功後才返回。
          • 同時threadA調用threadB的join()方法時也會被阻塞,當其它線程調用threadA的interrupt()方法中斷threadA時,threadA會拋出InterruptedException異常並返回。
          • join()方法阻塞的是當前線程,比如主線程中執行threadA.join(),阻塞的是主線程。而不是threadA線程。
        • yield:讓出CPU執行權的方法

          • 當一個線程調用yield()方法時,就是表明當前線程請求讓出自己的CPU使用權,但是線程調度器可以忽視這個請求。

          • 操作系統中線程的調度是按照時間片進行分配CPU執行權的,當一個線程使用完自己的時間片之後,線程調度器纔會進行下一輪的線程調度,而當一個線程調用了yield()方法時,是告訴線程調度器自己的時間片還沒用完但是不想用了,線程調度器可以進行下一輪線程調度了。

            public class YieldTest implements Runnable {
                YieldTest(){
                    //創建並啓動線程
                    Thread thread = new Thread(this);
                    thread.start();
                }
            
            
                @Override
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        //當i=0時讓出CPU執行權,放棄時間片,進行下一輪調度
                        if (i%5==0){
                            System.out.println(Thread.currentThread()+"yield");
                            //當前線程讓出CPU執行權,放棄時間片,進行下一輪調度
                            Thread.yield();
                        }
                    }
                    System.out.println(Thread.currentThread()+"is over");
            
                }
                public static void main(String[] args) {
                    new YieldTest();
                    new YieldTest();
                    new YieldTest();
                }
            }
            

            在這裏插入圖片描述

          • 以上代碼中啓動了三個線程並且分別在i=0 時候調用了Thread.yield()方法,所以三個線程中的輸出語句並沒有連在一起,因爲輸出第一行後當前線程就讓出了CPU執行權,其它線程先使用CPU調用了其它方法。

        • 總結:sleep()與yield()的區別在於,當線程調用sleep()方法時線程會被阻塞掛起到指定的時間,這期間線程調度器並不會調度該線程。而使用yield()方法時,線程知識讓出自己的時間片,並沒有被阻塞掛起,而是處於就緒狀態,在線程調度器進行下一次調度時仍然參與線程的競爭調度,並且有可能調度到該線程。

    • 線程中斷

      • void interrupt():中斷線程,當線程A正在運行時,線程B可以調用線程A的interrupt()的方法來設置線程A的中斷標誌爲true並立即返回。設置標誌僅僅是設置標誌,線程A並沒有被中斷,它會繼續執行。但是若線程A調用了wait()、join()、yield()方法被阻塞掛起時,此時線程B調用線程A的interrupt()方法,線程A會在調用這些方法的地方拋出InterruptedException異常而返回。

      • boolean isInterrupted():檢測當前線程是否被中斷,如果是則返回true,否則返回false。

      • boolean interrupted():檢測當前線程是否被中斷,如果是則返回true否則返回false。與isInterrupted()不同的是該方法如果發現當前線程被中斷則會清除中斷標誌,並且該方法是static方法,可以直接通過Thread類調用,並且interrupted()內部是獲取當前調用線程的中斷標誌,而不是調用interrupted()方法的實例對象的中斷標誌。

            public static boolean interrupted() {
                return currentThread().isInterrupted(true);
            }
        

        例子如下:

        public class InterruptedTest {
            public static void main(String[] args) throws InterruptedException {
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (;;){
        
                        }
                    }
                });
                //啓動線程
                thread.start();
                //設置中斷標誌
                thread.interrupt();
                //獲取中斷標誌
                System.out.println("isInterrupted:"+thread.isInterrupted());
                //獲取中斷標誌並重置(此時獲取的是主線程的中斷標誌)
                System.out.println("isInterrupted:"+ Thread.interrupted());
                //獲取中斷標誌
                System.out.println("isInterrupted:"+ thread.isInterrupted());
                thread.join();
            }
        }
        

        在第二個獲取中斷標誌並重置的地方,此時獲取的中斷標誌其實是主線程的中斷標誌,

    • 死鎖

      • 概念:死鎖是指兩個或兩個以上的線程在執行過程中,因爭奪資源而造成的互相等待的現象,在沒有外力的情況下,這些線程會相互等待而無法執行下去。
      • 產生條件
        • 互斥:線程對已經獲取到的資源會排斥其它線程的使用,也就是該資源同時只能由一個線程佔用。

        • 循環等待:發生死鎖時,是由一個線程請求資源的環形的鏈造成的。也就是t0等待t1的資源,t1等待t2資源…tn等待t0的資源。

        • 不可剝奪:線程獲得資源之後就不可被其它線程搶佔,除非自己使用完成。

        • 佔用且等待:一個線程持有了一個資源但是又請求其它資源,而其它資源正在被其它線程佔用,因此當前線程就會阻塞。

          public class DeadLockTest {
              //創建資源
              private static Object objectA = new Object();
              private static Object objectB = new Object();
              public static void main(String[] args) {
                  //創建線程A
                  Thread threadA = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          synchronized (objectA){
                              System.out.println(Thread.currentThread()+"get ObjectA");
                              try {
                                  Thread.sleep(1000);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                              System.out.println(Thread.currentThread()+"wait get ObjectB");
                              synchronized (objectB){
                                  System.out.println(Thread.currentThread()+"get ObjectB");
                              }
                          }
                      }
                  });
                  //創建線程A
                  Thread threadB = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          synchronized (objectB){
                              System.out.println(Thread.currentThread()+"get ObjectB");
                              try {
                                  Thread.sleep(1000);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                              System.out.println(Thread.currentThread()+"wait get ObjectA");
                              synchronized (objectA){
                                  System.out.println(Thread.currentThread()+"get ObjectA");
                              }
                          }
                      }
                  });
                  threadA.start();
                  threadB.start();
              }
          }
          
          

          在以上代碼中線程A獲取到objectA資源,線程B獲取到了objectB資源。線程A休眠結束後會企圖獲取objectB資源,但是objectB資源正在被線程B持有,所以線程A會被阻塞而等待,而線程B休眠結束後企圖獲取objectA資源,objectA正在被線程A持有,所以線程A與線程B就陷入相互等待中,也就產生了死鎖。

      • 避免死鎖
        • 打破至少一個造成死鎖的條件,但是目前只有佔有和等待以及循環等待是可以打破的。
        • 造成死鎖的原因其實和申請資源的順序有關,使用資源申請的有序性原則就可以避免死鎖。
    • Deamon守護線程

      • Java中的線程分爲兩類,分別爲deamon線程和user線程(用戶線程),JVM啓動時會調用main函數,main函數所在的線程就是守護線程,其實JVM內部還啓動了好多守護線程,比如垃圾回收線程

      • 守護線程與用戶線程的區別爲當最後一個用戶線程結束時,JVM就會正常退出,而不管當前是否還有守護線程,也就是說守護線程不影響JVM的退出。因此只要有一個用戶線程沒有結束,正常情況下JVM就不會退出。

        創建一個守護線程如下:

        public class DaemonTest {
            public static void main(String[] args) {
                Thread daemonThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
        
                    }
                });
                daemonThread.setDaemon(true);
                daemonThread.start();
            }
        }
        
        

        只需要設置線程的daemon參數爲true即可。

      • 當用戶線程都執行結束之後,JVM會執行一個叫做DestroyJava VM的線程,該線程會等待所有用戶線程結束後終止JVM進程。

      • 總結:如果你希望在主線程結束後 JVM進程馬上結束,那麼在創建線程時可以將其設置爲守護線程,如果你希望在主線程結束之後子線程繼續工作,等子線程結束之後再結束JVM進程,那麼就將子線程設置爲用戶線程。

    • ThreadLocal

      • 概念:多線程訪問同一個共享變量時容易出現併發問題,特別是多個線程需要對一個共享變量寫入時, 爲了保證線程安全,在訪問共享變量時需要進行適當的同步。

      • 同步的措施一般是加鎖,但是使用加鎖的方式增加了性能的損耗,因此可以使用ThreadLocal來實現。

      • ThreadLocal是JDK提供的,提供了線程本地變量,也就是如果創建了一個TreadLocal變量,那麼訪問這個變量的每個線程都會有這個變量的一個本地副本。因此當多個線程操作這個ThreadLocal變量時,其實操作的是自己本地內存裏面的變量,從而避免了線程安全問題。

        public class ThreadLocalTest {
            //創建ThreadLocal變量
            static ThreadLocal<String> threadLocal = new ThreadLocal<>();
            //print函數
            static void print(String string){
                //打印當前線程本地內存中的threadLocal
                System.out.println(string+":"+threadLocal.get());
                //清除當前線程本地內存中的threadLocal
        //        threadLocal.remove();
            }
        
            public static void main(String[] args) {
                //創建線程
                Thread threadA = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //設置線程A中本地變量threadLocal的值
                        threadLocal.set("threadA");
                        //調用打印函數
                        print("threadA");
                        System.out.println("threadA remove "+threadLocal.get());
                    }
                });
                //創建線程
                Thread threadB = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //設置線程B中本地變量threadLocal的值
                        threadLocal.set("threadB");
                        //調用打印函數
                        print("threadB");
                        System.out.println("threadB remove "+threadLocal.get());
                    }
                });
                threadA.start();
                threadB.start();
            }
        }
        

        在這裏插入圖片描述
        執行threadLocal.remove()之後結果
        在這裏插入圖片描述

      • 以上代碼中創建了一個共享變量threadLocal,兩個線程,線程A和線程B,當我們在線程A中對共享變量設置值時,並不會影響線程B中threadLocal的值,線程中通過set方法設置threadLocal的值,其實設置的是線程中本地內存中的一個副本,這個副本線程B是訪問不了的。同時通過get獲取的是當前線程本地內存中的值。

      • 實現原理
        ThreadLocal的結構在這裏插入圖片描述

      • 通過圖中我們可以看到Thread類中有一個threadLocalsinheritableThreadLocals,它們都是ThreadLocalMap類型的變量,而ThreadLocalMap是一個定製化的HashMap。在默認情況下每個線程中的threadLocals和inheritableThreadLocals都爲null,當線程第一次調用set或get時纔會創建它們。

      • 每個線程的本地變量並不存在ThreadLocal實例裏面,而是存放在線程中的threadLocals變量中,因此ThreadLocal就是一個工具殼,通過set將值放入線程中的threadLocals中,通過get將值從threadLocals中取出來,也可以通過調用remove將當前線程的threadLocals中的值刪除。

      • 那麼爲什麼threadLocals爲什麼被設置成map結構,因爲一個線程可能關聯多個ThreadLocal變量

      • 每個線程內部都有一個threadLocals的成員變量,該變量類型爲HashMap,key爲ThreadLocal實例對象的引用value則是需要設置的每個線程的本地變量都存放在線程自己的內存變量threadLocals中,如果線程不死亡,那麼該變量會一直存在,因此會造成內存溢出,因此使用完畢應當調用remove刪除threadLoccals中的變量。

        從源碼中我們也可以看到是通過threadLocals來實現的

            public void set(T value) {
            	//獲取當前線程
                Thread t = Thread.currentThread();
                調用getMap,當前線程作爲key,去查找對應的線程變量
                ThreadLocalMap map = getMap(t);
                //如果map不爲空,則調用set方法,key爲當前ThreadLocal的實例對象引用,value是傳遞的值。
                if (map != null)
                    map.set(this, value);
                else
                	//如果是第一次則調用
                    createMap(t, value);
            }
            ThreadLocalMap getMap(Thread t) {
            	//在getMap中獲取的是當前線程的threadLocals
                return t.threadLocals;
            }
        

        另一個類lnheritableThreadLocal類是爲了解決子線程可以訪問父線程中設置的本地變量

        public class InheritableThreadLocal<T> extends ThreadLocal<T> {
            /**
           
             *
             * @param parentValue the parent thread's value
             * @return the child thread's initial value
             */
            protected T childValue(T parentValue) {
                return parentValue;
            }
        
            /**
             * Get the map associated with a ThreadLocal.
             *
             * @param t the current thread
             */
            ThreadLocalMap getMap(Thread t) {
               return t.inheritableThreadLocals;
            }
        
            /**
             * Create the map associated with a ThreadLocal.
             *
             * @param t the current thread
             * @param firstValue value for the initial entry of the table.
             */
            void createMap(Thread t, T firstValue) {
                t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
            }
        }
        
        • InheritableThreadLocal繼承ThreadLocal,並重寫三個方法。因此當調用set方法時,創建的是當前線程的inheritableThreadLocals變量的實例而不再是threadLocals,調用get方法時獲取當前線程內部的map變量時,獲取的是inheritableThreadLocals而不再是threadLocals。因此在InheritableThreadLocal的世界裏,變量inheritableThreadLocals替代了threadLocals。

整理了三天的併發編程的知識,也不算整理就是看着併發編程的文章抄的,也算給自己加深學習吧!

該知識來自Java併發編程!

依然是會敲代碼的湯姆貓!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章