天天看點

Java并發程式設計核心概念一覽

并行相關概念

同步和異步

同步和異步通常來形容一次方法的調用。同步方法一旦開始,調用者必須等到方法結束才能執行後續動作;異步方法則是在調用該方法後不必等到該方法執行完就能執行後面的代碼,該方法會在另一個線程異步執行,異步方法總是伴随着回調,通過回調來獲得異步方法的執行結果。

并發和并行

很多人都将并發與并行混淆在一起,它們雖然都可以表示兩個或者多個任務一起執行,但執行過程上是有差別的。并發是多個任務交替執行,多任務之間還是串行的;而并行是多個任務同時執行,和并發有本質差別。

對計算機而言,如果系統内隻有一個 CPU ,而使用多程序或者多線程執行任務,那麼這種情況下多線程或者多程序就是并發執行,并行隻可能出現在多核系統中。當然,對 Java 程式而言,我們不必去關心程式是并行還是并發。

臨界區

臨界區表示的是多個線程共享但同時隻能有一個線程使用它的資源。在并行程式中臨界區資源是受保護的,必須確定同一時刻隻有一個線程能使用它。

阻塞

如果一個線程占有了臨界區的資源,其他需要使用這個臨界區資源的線程必須在這個臨界區進行等待(線程被挂起),這種情況就是發生了阻塞(線程停滞不前)。

死鎖\饑餓\活鎖

死鎖就是多個線程需要其他線程的資源才能釋放它所擁有的資源,而其他線程釋放這個線程需要的資源必須先獲得這個線程所擁有的資源,這樣造成了沖突無法解開;如圖1情形就是發生死鎖現象:

Java并發程式設計核心概念一覽

圖1:生活中的死鎖現象

活鎖就是兩個線程互相謙讓資源,結果就是誰也拿不到資源導緻活鎖;就好比過馬路,行人給車讓道,車又給行人讓道,結果就是車和行人都停在那不走。

饑餓就是,某個線程優先級特别低老是拿不到資源,導緻這個線程一直無法執行。

并發級别

并發級别分為阻塞,無饑餓,無障礙,無鎖,無等待幾個級别;根據名字我們也能大概猜出這幾個級别對應的什麼情形;阻塞,無饑餓和無鎖都好了解;我們說一下無障礙和無等待;

無障礙:無障礙級别預設各個線程不會發生沖突,不會互相搶占資源,一旦搶占資源就認為線程發生錯誤,進行復原。

無等待:無等待是在無鎖上的進一步優化,限制每個線程完成任務的步數。

并行的兩個定理

加速比:加速比=優化前系統耗時/優化後系統耗時

Amdahl 定理: 加速比=1/[F+(1-F)/n] 其中 n 表示處理器個數 ,F是程式中隻能串行執行的比例(串行率);由公式可知,想要以最小投入,得到最高加速比即 F+(1-F)/n 取到最小值,F 和 n 都對結果有很大影響,在深入研究就是數學問題了。

Gustafson 定律: 加速比=n-F(n-1),這兩定律差別不大,都展現了單純的減少串行率,或者單純的加 CPU 都無法得到最優解。

Java 中的并行基礎

原子性,可見性,有序性

原子性指的是一個操作是不可中斷的,要麼成功要麼失敗,不會被其他線程所幹擾;比如 ​

​int=1​

​ ,這一操作在 cpu 中分為好幾個指令,但對程式而言這幾個指令是一體的,隻有可能執行成功或者失敗,不可能發生隻執行了一半的操作;對不同 CPU 而言保證原子性的的實作方式各有不同,就英特爾 CPU 而言是使用一個 lock 指令來保證的。

可見性指某一線程改變某一共享變量,其他線程未必會馬上知道。

有序性指對一個操作而言指令是按一定順序執行的,但編譯器為了提高程式執行的速度,會重排程式指令;cpu在執行指令的時候采用的是流水線的形式,上一個指令和下一個指令差一個工步。比如A指令分三個工步:

  1. 操作記憶體a;
  2. 操作記憶體b;
  3. 操作記憶體c;

現假設有個指令 B 操作流程和 A 一樣,那麼先執行指令 A 再執行指令 B 時間全利用上了,中間沒有停頓等待;但如果有三個這樣的指令在流水線上執行: ​

​a>b>c​

​ , ​

​b>e>c​

​c>e>a​

​ ;這樣的指令順序就會發生等待降低了 CPU 的效率,編譯器為了避免這種事情發生,會适當優化指令的順序進行重排。

volatile關鍵字

volatile 關鍵字在 Java 中的作用是保證變量的可見性和防止指令重排。

線程的相關操作

建立線程有三種方法

  • 繼承Thread類建立線程
  • 實作Runnable接口建立線程
  • 使用Callable和Future建立線程

終止線程的方法

終止線程可調用 stop() 方法,但這個方法是被廢棄不建議使用的,因為強制終止一個線程會引起資料的不一緻問題。比如一個線程資料寫到一半被終止了,釋放了鎖,其他線程拿到鎖繼續寫資料,結果導緻資料發生了錯誤。終止線程比較好的方法是“讓程式自己終止”,比如定義一個辨別符,當辨別符為 true 的時候直讓程式走到終點,這樣就能達到“自己終止”的目的。

線程的中斷等待和通知

interrupt() 方法可以中斷目前程式,object.wait() 方法讓線程進入等待隊列,object.notify() 随機喚醒等待隊列的一個線程, object.notifyAll() 喚醒等待隊列的所有線程。object.wait() 必須在 synchronzied 語句中調用;執行wait、notify 方法必須獲得對象的螢幕,執行結束後釋放螢幕供其他線程擷取。

join

join() 方法功能是等待其他線程“加入”,可以了解為将某個線程并為自己的子線程,等子線程走完或者等子線程走規定的時間,主線程才往下走;join 的本質是調用調用線程對象的 wait 方法,當我們執行 wait 或者 notify 方法不應該擷取線程對象的監聽器,因為可能會影響到其他線程的 join。

yield

yield 是線程的“謙讓”機制,可以了解為當線程搶到 cpu 資源時,放棄這次資源重新搶占,yield() 是 Thread 裡的一個靜态方法。

線程組

如果一個多線程系統線程數量衆多而且分工明确,那麼可以使用線程組來分類。

  1. public void contextLoads() {

  2. ThreadGroup testGroup=new ThreadGroup("testGroup");

  3. Thread a = new Thread(testGroup, new MyRunnable(), "a");

  4. Thread b = new Thread(testGroup, new MyRunnable(), "b");

  5. a.start();

  6. b.start();

  7. int i = testGroup.activeCount();

  8. }

  9. class MyRunnable implements Runnable{

  10. @Override

  11. public void run() {

  12. System.out.println("test");

  13. }

  14. }

圖示代碼建立了一個 ​

​testGroup​

​ 線程組。

守護線程

守護線程是一種特殊線程,它類似 Java 中的異常系統,主要是概念上的分類,與之對應的是使用者線程。它功能應該是在背景完成一些系統性的服務;設定一個線程為守護線程應該線上程 start 之前 setDaemon()。

線程優先級

Java 中線程可以有自己的優先級,優先級高的更有優勢搶占資源;線程優先級高的不一定能搶占到資源,隻是一個機率問題,而對應優先級低的線程可能會發生饑餓。

在 Java 中使用1到10表示線程的優先級,使用setPriority()方法來進行設定,數字越大代表優先級越高。

Java 線程鎖

以下分類是從多個同角度來劃分,而不是以某一标準來劃分,請注意:

  • 阻塞鎖:當一個線程獲得鎖,其他線程就會被阻塞挂起,直到搶占到鎖才繼續執行,這樣會導緻 CPU 切換上下文,切換上下文對 CPU 而言是很耗費時間的。
  • 非阻塞鎖:當一個線程獲得鎖,其他線程直接跳過鎖資源相關的代碼繼續執行,就是非阻塞鎖。
  • 自旋鎖:當一個線程獲得鎖,其他線程則在不停進行空循環,直到搶到鎖,這樣做的好處是避免了上下文切換。
  • 可重入鎖:也叫做遞歸鎖,當一個線程獲得該鎖後,可以多次進入該鎖所同步着的代碼塊。
  • 互斥鎖:互斥鎖保證了某一時刻隻能有一個線程占有該資源。
  • 讀寫鎖:将代碼功能分為讀和寫,讀不互斥,寫互斥。
  • 公平鎖/非公平鎖:公平鎖就是在等待隊列裡排最前面的的先獲得鎖,非公平鎖就是誰搶到誰用。
  • 重量級鎖/輕量級鎖/偏向鎖:使用作業系統 “Mutex Lock” 功能來實作鎖機制的叫重量級鎖,因為這種鎖成本高;輕量級鎖是對重量級鎖的優化,提高性能;偏向鎖是對輕量級鎖的優化,在無多線程競争的情況下盡量減少不必要的輕量級鎖執行路徑。

synchronized

屬于阻塞鎖、互斥鎖、非公平鎖以及可重入鎖,在 JDK1.6 以前屬于重量級鎖,後來做了優化。

用法:

  • 指定加鎖對象
  • 用于靜态代碼塊/方法
  • 用于動态代碼塊/方法

示例:

  1. public static synchronized void test1(){

  2. System.out.println("test");

  3. }

  4. public synchronized void test2(){

  5. System.out.println("test");

  6. }

  7. public void test3(){

  8. synchronized (Main.class){

  9. System.out.println("test");

  10. }

  11. }

當鎖加在靜态代碼塊上或者靜态方法上或者為 ​

​synchronized(xxx.class){}​

​ 時,鎖作用于整個類,凡是屬于這個類的對象的相關都會被上鎖,當用于動态方法或者為或者為synchronized (object){}時鎖作用于對象;除此之外,synchronized可以保證線程的可見性和有序性。

Lock

Lock 是一個接口,其下有多個實作類。

方法說明:

  • lock()方法是平常使用得最多的一個方法,就是用來擷取鎖。如果鎖已被其他線程擷取,則進行等待。
  • tryLock()方法是有傳回值的,它表示用來嘗試擷取鎖,如果擷取成功,則傳回true,如果擷取失敗(即鎖已被其他線程擷取),則傳回false,這個方法還可以設定一個擷取鎖的等待時長,如果時間内擷取不到直接傳回。
  • 兩個線程同時通過lock.lockInterruptibly()想擷取某個鎖時,假若此時線程A擷取到了鎖,而線程B隻有在等待,那麼對線程B調用threadB.interrupt()方法能夠中斷線程B的等待過程。
  • unLock()方法是用來釋放鎖。
  • newCondition():生成一個和線程綁定的Condition執行個體,利用該執行個體我們可以讓線程在合适的時候等待,在特定的時候繼續執行,相當于得到這個線程的wait和notify方法。

ReentrantLock

ReentrantLock 重入鎖,是實作 Lock 接口的一個類,它對公平鎖和非公平鎖都支援,在構造方法中傳入一個 boolean 值,true 時為公平鎖,false 時為非公平鎖。

Semaphore(信号量)

信号量是對鎖的擴充,鎖每次隻允許一個線程通路一個資源,而信号量卻可以指定多個線程通路某個資源,信号量的構造函數為

  1. public Semaphore(int permits) {

  2. sync = new NonfairSync(permits);

  3. }

  4. public Semaphore(int permits, boolean fair) {

  5. sync = fair ? new FairSync(permits) : new NonfairSync(permits);

  6. }

第一個方法指定了可使用的線程數,第二個方法的布爾值表示是否為公平鎖。

acquire() 方法嘗試獲得一個許可,如果擷取不到則等待;tryAcquire() 方法嘗試擷取一個許可,成功傳回 true,失敗傳回false,不會阻塞,tryAcquire(int i) 指定等待時間;release() 方法釋放一個許可。

ReadWriteLock

讀寫分離鎖, 讀寫分離鎖可以有效的減少鎖競争,讀鎖是共享鎖,可以被多個線程同時擷取,寫鎖是互斥隻能被一個線程占有,ReadWriteLock 是一個接口,其中 readLock() 獲得讀鎖,writeLock() 獲得寫鎖 其實作類 ReentrantReadWriteLock 是一個可重入得的讀寫鎖,它支援鎖的降級(在獲得寫鎖的情況下可以再持有讀鎖),不支援鎖的更新(在獲得讀鎖的情況下不能再獲得寫鎖);讀鎖和寫鎖也是互斥的,也就是一個資源要麼被上了一個寫鎖,要麼被上了多個讀鎖,不會發生這個資即被上寫鎖又被上讀鎖的情況。

cas

cas(比較替換):無鎖政策的一種實作方式,過程為擷取到變量舊值(每個線程都有一份變量值的副本),和變量目前的新值做比較,如果一樣證明變量沒被其他線程修改過,這個線程就可以更新這個變量,否則不能更新;通俗的說就是通過不加鎖的方式來修改共享資源并同時保證安全性。

使用cas的話對于屬性變量不能再用傳統的 int ,long 等;要使用原子類代替原先的資料類型操作,比如 AtomicBoolean,AtomicInteger,AtomicInteger 等。

并發下集合類

并發集合類主要有:

  • ConcurrentHashMap:支援多線程的分段哈希表,它通過将整個哈希表分成多段的方式減小鎖粒度。
  • ConcurrentSkipListMap:ConcurrentSkipListMap的底層是通過跳表來實作的。跳表是一個連結清單,但是通過使用“跳躍式”查找的方式使得插入、讀取資料時複雜度變成了O(logn)。
  • ConCurrentSkipListSet:參考 ConcurrentSkipListMap。
  • CopyOnWriteArrayList:是 ArrayList 的一個線程安全的變形,其中所有可變操作(添加、設定,等等)都是通過對基礎數組進行一次新的複制來實作的。
  • CopyOnWriteArraySet:參考 CopyOnWriteArrayList。
  • ConcurrentLinkedQueue:cas 實作的非阻塞并發隊列。

線程池

介紹

多線程的設計優點是能很大限度的發揮多核處理器的計算能力,但是,若不控制好線程資源反而會拖累cpu,降低系統性能,這就涉及到了線程的回收複用等一系列問題;而且本身線程的建立和銷毀也很耗費資源,是以找到一個合适的方法來提高線程的複用就很必要了。

線程池就是解決這類問題的一個很好的方法:線程池中本身有很多個線程,當需要使用線程的時候拿一個線程出來,當用完則還回去,而不是每次都建立和銷毀。在 JDK 中提供了一套 Executor 線程池架構,幫助開發人員有效的進行線程控制。

Executor 使用

獲得線程池的方法:

  • newFixedThreadPool(int nThreads) :建立固定數目線程的線程池。
  • newCachedThreadPool:建立一個可緩存的線程池,調用execute将重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則建立一個新線 程并添加到池中。
  • newSingleThreadExecutor:建立一個單線程化的 Executor。
  • newScheduledThreadPool:建立一個支援定時及周期性的任務執行的線程池。

以上方法都是傳回一個 ExecutorService 對象,executorService.execute() 傳入一個 Runnable 對象,可執行一個線程任務。

下面看示例代碼

  1. public class Test implements Runnable{

  2. int i=0;

  3. public Test(int i){

  4. this.i=i;

  5. }

  6. public void run() {

  7. System.out.println(Thread.currentThread().getName()+"====="+i);

  8. }

  9. public static void main(String[] args) throws InterruptedException {

  10. ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

  11. for(int i=0;i<10;i++){

  12. cachedThreadPool.execute(new Test(i));

  13. Thread.sleep(1000);

  14. }

  15. }

  16. }

線程池是一個龐大而複雜的體系,本文定位是基礎,不對其做更深入的研究,感興趣的小夥伴可以自行查資料進行學習。

ScheduledExecutorService

newScheduledThreadPool(int corePoolSize) 會傳回一個ScheduledExecutorService 對象,可以根據時間對線程進行排程;其下有三個執行線程任務的方法:schedule(),scheduleAtFixedRate() 以及 scheduleWithFixedDelay() 該線程池可解決定時任務的問題。

  1. class Test implements Runnable {

  2. private String testStr;

  3. Test(String testStr) {

  4. this.testStr = testStr;

  5. }

  6. @Override

  7. public void run() {

  8. System.out.println(testStr + " >>>> print");

  9. }

  10. public static void main(String[] args) {

  11. ScheduledExecutorService service = Executors.newScheduledThreadPool(10);

  12. long wait = 1;

  13. long period = 1;

  14. service.scheduleAtFixedRate(new MyScheduledExecutor("job1"), wait, period, TimeUnit.SECONDS);

  15. service.scheduleWithFixedDelay(new MyScheduledExecutor("job2"), wait, period, TimeUnit.SECONDS);

  16. scheduledExecutorService.schedule(new MyScheduledExecutor("job3"), wait, TimeUnit.SECONDS);//延時waits 執行

  17. }

  18. }

job1的執行方式是任務發起後間隔 ​

​wait​

​ 秒開始執行,每隔 ​

​period​

​ 秒(注意:不包括上一個線程的執行時間)執行一次;

job2的執行方式是任務發起後間隔 ​

​wait​

​ 秒開始執行,等線程結束後隔 ​

​period​

​ 秒開始執行下一個線程;

job3隻執行一次,延遲 ​

​wait​

​ 秒執行;

ScheduledExecutorService 還可以配合 Callable 使用來回調獲得線程執行結果,還可以取消隊列中的執行任務等操作,這屬于比較複雜的用法,我們這裡掌握基本的即可,到實際遇到相應的問題時我們在現學現用,節省學習成本。

鎖優化

減小鎖持有時間

減小鎖的持有時間可有效的減少鎖的競争。如果線程持有鎖的時間越長,那麼鎖的競争程度就會越激烈。是以,應盡可能減少線程對某個鎖的占有時間,進而減少線程間互斥的可能。

減少鎖持有時間的方法有:

  • 進行條件判斷,隻對必要的情況進行加鎖,而不是整個方法加鎖。
  • 減少加鎖代碼的行數,隻對必要的步驟加鎖。

減小鎖粒度

減小鎖的範圍,減少鎖住的代碼行數可減少鎖範圍,減小共享資源的範圍也可減小鎖的範圍。減小鎖共享資源的範圍的方式比較常見的有分段鎖,比如 ​

​ConcurrentHashMap​

​ ,它将資料分為了多段,當需要 put 元素的時候,并不是對整個 hashmap 進行加鎖,而是先通過 hashcode 來知道他要放在那一個分段中,然後對這個分段進行加鎖,是以當多線程 put 的時候,隻要不是放在一個分段中,就實作了真正的并行的插入。

鎖分離

鎖分離最常見的操作就是讀寫分離了,讀寫分離的操作參考 ReadWriteLock 章節,而對讀寫分離進一步的延伸就是鎖分離了。為了提高線程的并行量,我們可以針對不同的功能采用不同的鎖,而不是統統用同一把鎖。比如說有一個同步方法未進行鎖分離之前,它隻有一把鎖,任何線程來了,隻有拿到鎖才有資格運作,進行鎖分離之後就不是這種情形了——來一個線程,先判斷一下它要幹嘛,然後發一個對應的鎖給它,這樣就能一定程度上提高線程的并行數。

鎖粗化

一般為了保證多線程間的有效并發,會要求每個線程持有鎖的時間盡量短,也就是說鎖住的代碼盡量少。但是如果如果對同一個鎖不停的進行請求、同步和釋放,其本身也會消耗系統寶貴的資源,反而不利于性能的優化 。比如有三個步驟:a、b、c,a同步,b不同步,c同步;那麼一個線程來時候會上鎖釋放鎖然後又上鎖釋放鎖。這樣反而可能會降低線程的執行效率,這個時候我們将鎖粗化可能會更好——執行 a 的時候上鎖,執行完 c 再釋放鎖。

鎖擴充

分布式鎖

JDK 提供的鎖在單體項目中不會有什麼問題,但是在叢集項目中就會有問題了。在分布式模型下,資料隻有一份(或有限制),此時需要利用鎖的技術控制某一時刻修改資料的程序數。JDK 鎖顯然無法滿足我們的需求,于是就有了分布式鎖。

分布式鎖的實作有三種方式:

  • 基于資料庫實作分布式鎖
  • 基于緩存(redis,memcached,tair)實作分布式鎖
  • 基于 Zookeeper 實作分布式鎖

基于redis的分布式鎖比較使用普遍,在這裡介紹其原理和使用:

redis 實作鎖的機制是 setnx 指令,setnx 是原子操作指令,鎖存在不能設定值,傳回 0 ;鎖不存在,則設定鎖,傳回 1 ,根據傳回值來判斷上鎖是否成功。看到這裡你可能想為啥不先 get 有沒有值,再 set 上鎖;首先我們要知道,redis 是單線程的,同一時刻隻可能有一個線程操作記憶體,然後 setnx 是一個操作步驟(具有原子性),而 get 再 set 是兩個步驟(不具有原子性)。如果使用第二種可能會發生這種情況:用戶端 a get發現沒有鎖,這個時候被切換到用戶端b,b get也發現沒鎖,然後b set,這個時候又切換到a用戶端 a set;這種情況下,鎖完全沒起作用。是以,redis分布式鎖,原子性是關鍵。

對于 web 應用中 redis 用戶端用的比較多的是 lettuce,jedis,redisson。springboot 的 redis 的 start 包底層是 lettuce ,但對 redis 分布式鎖支援得最好的是 redisson(如果用 redisson 你就享受不到 redis 自動化配置的好處了);不過 springboot 的 redisTemplete 支援手寫 lua 腳本,我們可以通過手寫 lua 腳本來實作 redis 鎖。

代碼示例:

  1. public boolean lockByLua(String key, String value, Long expiredTime){

  2. String strExprie = String.valueOf(expiredTime);

  3. StringBuilder sb = new StringBuilder();

  4. sb.append("if redis.call(\"setnx\",KEYS[1],ARGV[1])==1 ");

  5. sb.append("then ");

  6. sb.append(" redis.call(\"pexpire\",KEYS[1],KEYS[2]) ");

  7. sb.append(" return 1 ");

  8. sb.append("else ");

  9. sb.append(" return 0 ");

  10. sb.append("end ");

  11. String script = sb.toString();

  12. RedisCallback<Boolean> callback = (connection) -> {

  13. return connection.eval(script.getBytes(), ReturnType.BOOLEAN, 2, key.getBytes(Charset.forName("UTF-8")),strExprie.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8")));

  14. };

  15. Boolean execute = stringRedisTemplate.execute(callback);

  16. return execute;

  17. }

關于lua腳本的文法我就不做介紹了。

在 github 上也有開源的 redis 鎖項目,比如 spring-boot-klock-starter 感興趣的小夥伴可以去試用一下。

資料庫鎖

對于存在多線程問題的項目,比如商品貨物的進銷存,訂單系統單據流轉這種,我們可以通過代碼上鎖來控制并發,也可以使用資料庫鎖來控制并發,資料庫鎖從機制上來說分樂觀鎖和悲觀鎖。

悲觀鎖:

悲觀鎖分為共享鎖(S鎖)和排他鎖(X鎖),MySQL 資料庫讀操作分為三種——快照讀,目前讀;快照讀就是普通的讀操作,如:

  1. select *from table

目前讀就是對資料庫上悲觀鎖了;其中 ​

​select...lockinshare mode​

​ 屬于共享鎖,多個事務對于同一資料可以共享,但隻能讀不能修改。而下面三種 SQL :

  1. select ...for update

  2. update ... set...

  3. insert into ...

屬于排他鎖,排他鎖就是不能與其他鎖并存,如一個事務擷取了一個資料行的排他鎖,其他事務就不能再擷取該行的其他鎖,包括共享鎖和排他鎖,但是擷取排他鎖的事務是可以對資料行讀取和修改,排他鎖是阻塞鎖。

樂觀鎖:

就是很樂觀,每次去拿資料的時候都認為别人不會修改,是以不會上鎖,但是在更新的時候會判斷一下在此期間别人有沒有去更新這個資料,如果有則更新失敗。一種實作方式為在資料庫表中加一個版本号字段 version ,任何 update 語句 where 後面都要跟上 version=?,并且每次 update 版本号都加 1。如果 a 線程要修改某條資料,它需要先 select 快照讀獲得版本号,然後 update ,同時版本号加一。這樣就保證了在 a 線程修改某條資料的時候,確定其他線程沒有修改過這條資料,一旦其他線程修改過,就會導緻 a 線程版本号對不上而更新失敗(這其實是一個簡化版的mvcc)。

樂觀鎖适用于允許更新失敗的業務場景,悲觀鎖适用于確定更新操作被執行的場景。

并發程式設計相關

  • 善用 Java8 Stream
  • 對于生産者消費者模式,條件判斷是使用 while 而不是 if
  • 懶漢單例采用雙重檢查和鎖保證線程安全
  • 善用 Future 模式
  • 合理使用 ThreadLocal

Java 8 引入 lambda 表達式使在 Java 中使用函數式程式設計很友善。而 Java 8 中的 stream 對資料的處理能使線程執行速度得以優化。Future 模式是一種對異步線程的回調機制;現在 cpu 都是多核的,我們在處理一些較為費時的任務時可使用異步,在背景開啟多個線程同時處理,等到異步線程處理完再通過 Future 回調拿到處理的結果。

ThreadLocal 的執行個體代表了一個線程局部的變量,每條線程都隻能看到自己的值,并不會意識到其它的線程中也存在該變量(這裡原理就不說了,網上資料很多),總之就是我們如果想在多線程的類裡面使用線程安全的變量就用 ThreadLocal ,但是請一定要注意用完記得 remove ,不然會發生記憶體洩漏。