JAVA多線程高併發基礎知識點

JAVA多線程高併發基礎知識點

本文中的代碼java版本:jdk11.

1. 線程基礎

1.1. 線程概念

線程是操作系統能夠進行運算調度的最小單位(程序執行流的最小單元)。

它被包含在進程之中,是進程中的實際運作單位。

一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。

1.2. 線程安全

  1. 原子性:線程共享內存同一時間只有一個線程可以操作(synchronized關鍵字,互斥鎖)。
  2. 可見性:某個線程的操作可以立即被其他線程觀察到(volatile關鍵字,強制刷新線程內該變量內存)。
  3. 有序性:線程內指令順序執行(現代CPU指令重排,亂序執行);在java中指令重排不影響單線程內的最終結果,但不保證多線程。

1.3. 創建並啓動線程的方法

  1. 實現 Runnable 接口
class ByRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("實現Runnable並實現run方法");
    }
}
Thread thread = new Thread(new ByRunnable());
thread.start();//調用start以開啓新線程
thread.join();//阻止主線程在新線程之前結束
  1. 繼承 Thread 類
class ByThread extends Thread{
    @Override
    public void run() {
        System.out.println("繼承Thread並重寫run方法");
    }
}
Thread thread = new ByThread();
thread.start();
thread.join();
  1. 守護線程
Thread thread = new Thread(()->System.out.println("lambda thread"));
//將線程設置爲守護線程
//JVM會在所有非守護線程結束後自動退出,
//守護線程不會隨着JVM退出而結束,
//因此守護線程如果持有需要關閉的資源,可能會因無法正常關閉資源(JVM退出)而造成嚴重後果,
//如打開文件可能會導致數據丟失.
thread.setDaemon(true);
thread.start();

1.4. 線程狀態

使用Thread.currentThread().getState();可以獲得當前線程狀態,thread.getState();獲得某一線程的狀態。

  1. 初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。
  2. 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱爲“運行”。 線程對象創建後,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得CPU時間片後變爲運行中狀態(running)。
  3. 阻塞(BLOCKED):表示線程阻塞於鎖。
  4. 等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)。
  5. 超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間後自行返回。
  6. 終止(TERMINATED):表示該線程已經執行完畢。 java線程狀態轉換

1.5. 線程超時等待(TIMED_WAITING)相關方法

  1. 線程睡眠(sleep),當持有鎖時不釋放鎖!且sleep的線程無法從外部喚醒,但可被中斷!

    try {
        //睡眠1000毫秒,在此期間不參與CPU資源競爭(線程調度)
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        //可能在睡眠結束前線程就被中斷了
        e.printStackTrace();
    }
    
  2. 線程讓出(yield)

    //給CPU一個提示,讓出本次線程調度,但參與下一次調度(進入線程等待隊列),CPU可能會忽略本次讓出
    Thread.yield();
    
  3. 線程加入(join)

    Thread t = new Thread(()->{
        System.out.println("new Thread.....");
    });
    t.start();
    //將線程t加入當前線程,即告訴當前線程等待線程t結束或在1000毫秒後再繼續
    t.join(1000);
    
  4. 線程超時等待(wait),只有持有鎖的對象才能調用這個方法且會釋放鎖!

    //使獲得鎖的obj對象所在線程進入等待,等待1000毫秒後自動被喚醒,或者被notify(notifyAll)喚醒
    obj.wait(1000);
    
  5. 線程超時禁用(park)

    //等待到當前時間的1000毫秒結束禁用
    LockSupport.parkUntil(System.currentTimeMillis()+1000);
    //禁用當前線程10000納秒
    LockSupport.parkNanos(10000);
    

1.6. 線程等待(WAITING)相關方法

  1. wait、notify、notifyAll:wait使當前獲得鎖的線程等待並釋放鎖,notify喚醒隨機一個等待的線程來競爭鎖及notifyAll喚醒所有等待的線程來競爭鎖,但notify操作不釋放鎖(只有持有鎖的對象才能調用這些方法,且notify和notifyAll只能喚醒在同一個鎖對象下wait的線程)

    //模擬倉庫
    ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
    //模擬生產者
    Thread producer = new Thread(()->{
        for(int i=0;;++i){
            try {
                while (queue.size() < 5) {
                    Thread.sleep(100);
                    queue.add("prod"+(++i));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (queue) {
                //隨機喚醒一個等待中的線程
                queue.notify();
                if(queue.size() >= 5) {
                    try {
                        //倉庫滿了使當前線程等待
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    });
    //模擬消費者
    Thread consumer = new Thread(()->{
        for(;;){
            try {
                while (queue.size() > 0 ) {
                    Thread.sleep(10);
                    System.out.println(queue.poll());
                }
            } catch (InterruptedException e){
                e.printStackTrace();
            }
            synchronized (queue) {
                //喚醒所有其他線程,此時最多隻有一個線程(生產者)在等待
                queue.notifyAll();
                if(queue.size()<=0) {
                    try {
                        //商品消費完了,使當前線程等待
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    });
    producer.start();
    consumer.start();
    producer.join();
    consumer.join();
    
  2. park和unpark:park使線程等待及unpark使線程結束等待,park時當持有鎖時不釋放鎖!可以在外部被unpark喚醒!

    Thread t = new Thread(()->{
        System.out.println("new.....");
        LockSupport.park();//讓線程進入等待
        System.out.println("over wait");
    });
    t.start();
    Thread.sleep(1000);
    System.out.println("main sleep 1000");
    LockSupport.unpark(t);//喚醒指定線程t
    /*控制檯輸出:
    new.....
    main sleep 1000
    over wait
    */
    

2. synchronized關鍵字

  1. synchronized是同步、互斥、獨佔、可重入的鎖
  2. synchronized鎖住的是對象,當沒有傳入對象時:
    1. 當對靜態方法加鎖時鎖住的是當前類的class對象。
    2. 當對實例方法枷鎖時鎖住的時當前實例對象(this)。
    3. 在方法內使用synchronized時必須傳入對象synchronized(obj){/*sync code*/}並鎖住其代碼塊。
  3. synchronized鎖住的對象纔可以使用wait、notify、notifyAll。
  4. synchronized存在鎖升級的概念
    1. 當始終同時只有同一個線程競爭時,鎖時處於偏向鎖狀態(markword只記錄線程標識而不嘗試加鎖且不主動釋放記錄的線程標識,此時默認不會有其他線程競爭鎖)。
    2. 在獲得鎖的線程未結束時有線程來競爭鎖,鎖升級爲輕量級鎖(自旋鎖,線程不調用系統函數進入CPU等待隊列)。
    3. 當競爭變大(10次)或者鎖持續時間變長時,鎖升級爲重量級鎖(調用系統函數使線程進入CPU等待隊列)。

3. volatile關鍵字

  • 保證線程可見性

    • 在java中有共享內存,也有線程內工作內存,當持有共享內存的值時先將共享內存中的值複製到工作內存,每次修改值時先修改工作內存,寫回到共享內存的時機由CPU控制(線程之間不可見),可能出現A線程改變某一個線程共享變量的值後B線程無法及時看到更新後的值.當使用volatile關鍵字後,每次修改該變量都會及時回寫到共享內存並通知其他線程.
    • 保證線程可見性的緩存一致性協議:Intel:MESI.
  • 禁止指令重排序

    • 指令不會被CPU和編譯器優化亂序執行,而是按照程序流線性執行.

    • volatile關鍵字在Double Check Lock(DCL)單例的應用 :

      public class Test {
          private static volatile Test INSTANCE;
          public int i;
          private Test(){i=100;}
          //懶漢單例
          public static final Test instance(){
              if (INSTANCE==null) {
                  synchronized (Test.class) {
                      //Double Check 雙重檢測
                      if (INSTANCE==null) {
                          //new一個對象分三步: 
                          //1.申請內存(對象成員變量賦初值);
                          //2.初始化成員變量(按照代碼要求賦值);
                          //3.將new對象賦值給變量.
                          //new的過程不是原子的,可以指令重排序.
                          //初始化單例時,volatile保證不會亂序執行,其他線程不會得到未完全初始化的單例.
                          //即如果指令重排序了,先賦值了對象變量,再初始化對象內成員變量,
                          //另一個線程得到的單例可能是未完全初始化的,對象的變量i可能是初值0
                          INSTANCE = new Test();
                      }
                  }
              }
              return INSTANCE;
          }
      }
      

volatile不可避免的會降低程序執行效率.

4. Atomic原子操作類及CAS

4.1. CAS

全稱: Compare And Swap (比較並交換) 或者說 Compare And Set.

以CAS做底層的鎖是一種輕量級樂觀鎖,當前線程不會進入等待隊列.默認很快就會得到鎖.

CAS是一種無鎖算法(不與內核通信,不放棄CPU,循環執行比較直到預期值,完成變量賦值).

CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B. 當且僅當預期值A和內存值V相同時,將內存值V修改爲B,否則什麼都不做.

AtomicInteger::incrementAndGet底層的CAS

//代碼位於Unsafe.class
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    return v;
}
@HotSpotIntrinsicCandidate
public final boolean weakCompareAndSetInt(Object o, long offset,
                                          int expected,
                                          int x) {
    return compareAndSetInt(o, offset, expected, x);
}
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
                                             int expected,
                                             int x);

CAS僞代碼實現

cas(value,expected,newValue){
    if(value == expected) value = newValue;
    else{
        //try again or fail
    }
}

CAS中的ABA問題

A線程對變量I的預期值爲0,由於從內存中獲取I需要時間,此時其他線程先將變量I改爲其他值後又改會0,雖然此時依然滿足A線程的預期值,但不是原來的預期值了,此時就發生了ABA問題,如果此時的偷樑換柱影響業務,一般的解決方式是加版本號.

4.2. 原子類

java原生提供了原子操作的對象,以用於線程間共享內存,atom類型在操作內存時主要使用了CAS模式.

通常情況下修改內存需要的時間都非常短暫,因此大多數情況下,在多個線程操作同一個變量時,原類型操作內存(CAS)的效率要高於使用同步代碼塊(synchronized)的效率

AtomicReference

包裹一個類型,使該變量的操作具有原子性,主要用於線程間共用變量

AtomicReference<Integer> ar = new AtomicReference<>();
new Thread(()->{
    ar.set(1234);
}).start();
new Thread(()->{
    System.out.println(ar.get());
}).start();

AtomicInteger

原子int類型,內部對數據的操作利用CAS模式

AtomicInteger ai = new AtomicInteger();
ai.incrementAndGet();

LongAdder

jdk1.8提供的用於超高併發情況下的Long類型計數器,以代替AtomicInteger或AtomicLong,在高併發下計算效率最高,並提供了更方便的操作方法.

LongAdder比AtomicLong更快的原因是採用了分段思想,使用了一個非volatile的Cell數組來存儲累積線程作爲分段鎖,當Cell數組不爲空時,

進行兩次CAS,第一次CAS當前操作的累積的Cell數組中的某個元素,第二次再CAS被volatile修飾的long值,

避免了直接修改long值時,許多線程CAS競爭時頻繁空轉,加快了CPU效率

LongAdder a = new LongAdder();
a.add(2);
int i = a.intValue();//返回當前的總和值並截斷爲int類型,返回的結果是原子操作後的
System.out.println(i);
a.increment();
System.out.println(a.intValue());
long l = a.sum();//當前的總和值,返回的結果是非原子的
System.out.println(l);

LongAdder::add

/**
 * Adds the given value.
 * @param x the value to add
 */
public void add(long x) {
    Cell[] cs; long b, v; int m; Cell c;
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[getProbe() & m]) == null ||
            //c.cas:第一次CAS,這個c是cs數組中的一個元素,cs是一個分段鎖
            !(uncontended = c.cas(v = c.value, v + x)))
            //這裏面會用CAS實際add真正的long值
            longAccumulate(x, null, uncontended);
    }
}

其他

所有java原生提供的原子類型都是在java.util.concurrent.atomic包下提供的

4.3. Unsafe類

獲取unSafe實例

Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe)theUnsafe.get(Unsafe.class);

利用Unsafe直接操作內存

long l = unsafe.allocateMemory(8);//分配內存
unsafe.setMemory(l,0L,(byte)0);//操作內存
unsafe.freeMemory(l);//釋放內存

利用Unsafe直接生成並操作對象

class My{
    int a = 111;
}
//直接生成實例
My o = (My) unsafe.allocateInstance(My.class);
long offset = unsafe.objectFieldOffset(My.class.getDeclaredField("a"));
//直接通過偏移量操作對象屬性
unsafe.getAndSetInt(o,offset,1234);
System.out.println(o.a);//輸出 1234

利用Unsafe操作CAS

unsafe.compareAndSwapInt(obj,offset,intVal);
unsafe.compareAndSwapLong(obj,offset,longVal);

5. 各種JUC包下的同步鎖

5.0 死鎖

死鎖的四個必要條件:

  1. 互斥:資源唯一,且線程互斥;
  2. 不可剝奪:已獲得資源後不可剝奪資源,只能主動釋放;
  3. 請求與保持:已獲得資源的線程嘗試獲得其他唯一資源,而該資源已被其他線程佔有;
  4. 循環等待:存在一種資源的循環等待鏈,鏈中每一個線程已獲得的資源同時被鏈中下一個線程所請求.

避免死鎖的核心思想: 打破死鎖的四個必要條件

  1. 加鎖順序
  2. 加鎖時限
  3. 死鎖檢測

避免死鎖的常用方法:

  1. 有序資源分配法
  2. 銀行家算法

解除死鎖的常用方法:

  1. 設置線程還原點
  2. 撤銷線程
  3. 剝奪資源

5.1. ReentrantLock

ReentrantLock是可重入鎖,且需要手動申請鎖,手動釋放鎖

嘗試申請鎖,申請不到不阻塞線程而是以無鎖繼續執行,返回true表示申請到鎖: ReentrantLock::tryLock

阻塞申請鎖,未申請到時將阻塞線程:ReentrantLock::lock

阻塞申請可以被打斷的鎖:ReentrantLock::lockInterruptibly,打斷鎖:Thread::interrupt,這種方式申請的鎖可中斷線程解除死鎖(捕捉中斷異常釋放或在finally塊中釋放)

釋放已經申請的鎖:ReentrantLock::unlock

公平鎖與非公平鎖的區別:

  • 非公平鎖:執行lock時直接爭搶鎖,synchronized關鍵字是非公平鎖。
  • 公平鎖:執行lock時,先檢查鎖等待隊列,如果隊列不爲空,則加入鎖隊列,否則直接爭搶鎖。
package test;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadTest {
    private static final String thStr(){
        return Thread.currentThread().getId()+" "+Thread.currentThread().getName();
    }

    public static class ReentrantLockTest {
        private final static Lock lock1 = new ReentrantLock();//入參爲true爲公平鎖,等待鎖最久將獲得鎖
        private final static Lock lock2 = new ReentrantLock();
        private static final void doSomething() {
            String str = thStr();
            try {
                System.out.println(str);
                lock1.lock();//獲得鎖
                System.out.println(str+" alloc lock");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(str+" will free lock");
                lock1.unlock();//釋放鎖一定要在finally塊中,以保證得到執行
            }
        }
        //常規獲得鎖
        public final static void testLock() {
            for (int i=0;i<10;i++){
                new Thread(()->{
                    doSomething();
                }).start();
            }
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //死鎖及解除死鎖
        public static final void deadLock() {
            //兩個線程相互獲取對方鎖
            var t1 = new Thread(new DeadLockThread(lock1,lock2));
            var t2 = new Thread(new DeadLockThread(lock2,lock1));
            t1.start();
            t2.start();
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t1.interrupt();//中斷線程1,使線程2獲得鎖(解除死鎖)
            try {
                TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        private static final class DeadLockThread implements Runnable {
            private Lock lock1,lock2;
            public DeadLockThread(Lock lock1, Lock lock2) {
                this.lock1 = lock1;
                this.lock2 = lock2;
            }
            @Override
            public void run() {
                String str = thStr();
                try {
                    lock1.lockInterruptibly();//獲得可被打斷的鎖(以實現可解除死鎖)
                    System.out.println(str+" acquire lock1");
                    TimeUnit.MILLISECONDS.sleep(100);
                    lock2.lockInterruptibly();
                    System.out.println(str+" acquire lock2");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(str+" will free lock1");
                    lock1.unlock();
                    System.out.println(str+" will free lock2");
                    lock2.unlock();
                }
            }
        }
        //超時休眠重新嘗試獲取鎖
        public static final void tryLockTest(){
            ArrayList<Thread> threads = new ArrayList<>(8);
            for(int i=0;i<4;i++){
                threads.add(new Thread(()->{
                    tryLock();
                }));
            }
            threads.forEach(thread -> thread.start());
            threads.forEach(t-> {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        private static final void tryLock(){
            String str = thStr();
            try {
                while (!lock1.tryLock(1,TimeUnit.SECONDS)){
                    System.out.println(str + " try acquire lock1 filed");
                    Thread.sleep(300);
                }
                System.out.println(str + " acquire lock1");
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(str+" attempt free lock1");
                lock1.unlock();
            }

        }
        //Condition等待和喚醒
        public static final void testCondition(){
            ArrayList<Thread> threads = new ArrayList<>();
            for (int i=0;i<20;i++) {
                if(i%2==0){
                    threads.add(new Thread(()->ConditionTest.get()));
                }else{
                    threads.add(new Thread(()->ConditionTest.put()));
                }
            }
            threads.forEach(t->t.start());
        }
        private static final class ConditionTest{
            private static final Condition condP = lock1.newCondition();
            private static final Condition condG = lock1.newCondition();
            private static final ArrayList<String> list = new ArrayList<>();
            private static final AtomicInteger ai = new AtomicInteger(0);
            public static final void put(){
                String str = thStr();
                try {
                    lock1.lock();
                    System.out.println(str+" try put");
                    if(list.size()>=3){
                        System.out.println(str+" list size >=3 will await");
                        condP.await(300,TimeUnit.MILLISECONDS);//線程進入等待,同時釋放鎖
                    }
                    list.add("item "+ai.incrementAndGet());
                    if(list.size()>=1){
                        System.out.println(str+" now list size "+list.size());
                        condG.signalAll();//喚醒等待的鎖
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(str+" unlock put");
                    lock1.unlock();
                }
            }
            public static final void get(){
                String str = thStr();
                try {
                    lock1.lock();
                    System.out.println(str+" try get");
                    if(list.size()<=0){
                        System.out.println(str+" list size <=0 will await");
                        condG.await();
                    }
                    try {
                        System.out.println(str+" "+list.remove(0));
                    } catch (RuntimeException e){
                        System.err.println(str+" list size is zero, will retry");
                        get();
                    }
                    if(list.size()<3)
                        condP.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(str+" unlock get");
                    lock1.unlock();
                }
            }
        }
    }
}

5.2. ReadWriteLock 和 StampedLock

讀寫鎖同時實現了共享鎖(讀)和排他鎖(寫).

讀鎖對所有讀線程共享,寫鎖對所有操作線程排他.

爲滿足強一致性,ReadWriteLock讀鎖採用悲觀鎖,即如果有線程正在讀,寫線程需要等待讀線程釋放鎖後才能獲取寫鎖.

讀寫鎖可能造成活鎖(某些線程一直拿不到鎖).

讀鎖可以避免讀到寫入一半的數據,寫鎖避免寫入混亂.

在寫少讀多的場景下ReadWriteLock性能會明顯高於ReentrantLock.

StampedLockReadWriteLock的改進版,主要改進了讀鎖爲樂觀鎖,提高了併發性能,但代價是可能讀取到中間數據,需要業務自己判斷取捨.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test {

    public static void main(String[] args) throws Exception {
        threadTest();
    }
    static void threadTest() throws Exception {
        var l = new ReentrantReadWriteLock();
        var rl = l.readLock();
        var wl = l.writeLock();
        var ts = new Thread[9];
        for(var i=0;i<9;++i){
            final var iF = i;
            ts[i] = new Thread(()->{
                for(var j=0;j<2;++j){
                    //寫用寫鎖
                    if(iF/4==0 && j==1)write(wl,iF+j);
                    //讀用讀鎖
                    else read(rl);
                }
            });
        }
        for (Thread t : ts) {
            t.start();
            t.join();
        }
    }
    private static int i;
    private static void read(Lock l){
        try {
            l.lock();
            Thread.sleep(100);
            System.out.println("read "+i);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            l.unlock();
        }
    }
    private static void write(Lock l, int v){
        try {
            l.lock();
            Thread.sleep(50);
            System.out.println("write "+v);
            i = v;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            l.unlock();
        }
    }
}
/*控制檯輸出: 
read 0
write 1
read 1
write 2
read 2
write 3
read 3
write 4
read 4
read 4
read 4
read 4
read 4
read 4
read 4
read 4
read 4
read 4
*/
import java.util.concurrent.locks.StampedLock;

public class Test {

    public static void main(String[] args) throws Exception {
        threadTest();
    }
    static void threadTest() throws Exception {
        var l = new StampedLock();
        var ts = new Thread[9];
        for(var i=0;i<9;++i){
            final var iF = i;
            ts[i] = new Thread(()->{
                for(var j=0;j<2;++j){
                    if(iF%4==0)write(l,iF+j);
                    else read(l);
                }
            });
        }
        for (Thread t : ts) {
            t.start();
            t.join();
        }
    }
    private static int i;
    private static void read(StampedLock l){
        var stamp = l.tryOptimisticRead();
        // 檢查讀鎖後是否有其他寫鎖發生
        if(l.validate(stamp)){
            try {
                //樂觀讀鎖
                stamp = l.readLock();
                Thread.sleep(100);
                System.out.println("read "+i);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                l.unlockRead(stamp);
            }
        }else{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("has write");
        }
    }
    private static void write(StampedLock l, int v){
        //寫鎖
        var stamp = l.writeLock();
        try {
            Thread.sleep(50);
            System.out.println("write "+v);
            i = v;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            l.unlockWrite(stamp);
        }
    }
}

5.3. CountDownLatch

一個類似於門閂的同步工具類,可以阻塞當前線程,直到計數歸零或到達超時時間。

CountDownLatch只能countDown,這意味着只能阻塞一次。

如果在調用CountDownLatch::await時計數已經歸零則不會阻塞當前線程,否則阻塞當前線程直到計數歸零。

使用CountDownLatch::countDown使計數減一。

一個線程可以調用多次countDown,是計數多次減一。

主要用來開啓多個線程處理任務,並在全部處理結束後通知主線程處理接下來的任務。

//創建一個從2開始計數的CountDownLatch
CountDownLatch cd = new CountDownLatch(2);
for(int i=0;i<2;++i){
    new Thread(()->{
        try {
            Thread.sleep(1000);
        }catch (Exception e){}
        finally {
            //當線程完成自己的任務後cd計數減一
            cd.countDown();
        }
    }).start();
}
//阻塞當前線程直到cd計數歸零
cd.await();
System.out.println("main");

5.4. CyclicBarrier

一個類似於柵欄的同步工具類,當計數器到達上限時喚醒所有阻塞的線程(放行)並可以執行指定任務。

CyclicBarrier可以循環使用,當到達臨界值時放行後自動歸零並重用。

CyclicBarrier可以用來做同步(最後一個線程調用await後才能放行全部線程)。

調用CyclicBarrier::await使線程等待,直到等待的線程到達CyclicBarrier的臨界值,所有await的線程將被喚醒。

由於使用await相較於CountDownLatch來說一個線程在一次"推倒柵欄"之前只能使計數器加一一次。

//創建一個臨界值爲3的CyclicBarrier對象,並在計數到達3時執行指定任務,這個任務將先於被放行的線程執行
CyclicBarrier cb = new CyclicBarrier(3, ()->{
    //這段代碼將使用最後一個執行await的線程執行,然後所有線程將執行其後的操作
    System.out.println(Thread.currentThread().getName()+" ========== we go~ time:"+new Date().getTime());
});
for(int i=0;i<3;++i){
    int iF= i;
    new Thread(()->{
        try {
            Thread.sleep(iF*1000);
            System.out.println(Thread.currentThread().getName()+" "+iF+" wait time:"+new Date().getTime());
            //等待柵欄打開,這個動作會使計數器加一,當加到臨界值時全部等待的線程將開始執行
            cb.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        } finally {
            //cb.reset();//重置計數
            //柵欄打開後將執行的任務,放行後柵欄重置,可以再次await
            System.out.println(Thread.currentThread().getName()+" "+iF+" go time:"+new Date().getTime());
            try {
                Thread.sleep(4000-iF*1000);
                System.out.println(Thread.currentThread().getName()+" "+"| "+iF+" wait2 time:"+new Date().getTime());
                //再次等待柵欄打開
                cb.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+" "+"| "+iF+" go2 time:"+new Date().getTime());
            }
        }
    }).start();
}
/*控制檯輸出:
Thread-0 0 wait time:1616147361601
Thread-1 1 wait time:1616147362604
Thread-2 2 wait time:1616147363613
Thread-2 ========== we go~ time:1616147363613
Thread-0 0 go time:1616147363623
Thread-1 1 go time:1616147363623
Thread-2 2 go time:1616147363623
Thread-2 | 2 wait2 time:1616147365631
Thread-1 | 1 wait2 time:1616147366631
Thread-0 | 0 wait2 time:1616147367633
Thread-0 ========== we go~ time:1616147367633
Thread-0 | 0 go2 time:1616147367633
Thread-2 | 2 go2 time:1616147367633
Thread-1 | 1 go2 time:1616147367633
*/

5.5. Phaser

一個階段性同步工具類,當所有線程到達某一階段時進入下一階段,可以看作CyclicBarrier的升級版。

每當所有線程都執行了arrive則進入下一階段。直到所有線程都註銷(deregister)了則流程結束。

每一個Phaser都允許有父Phaser,這樣可以將多個Phaser串聯組成一個複雜的流程。

Phaser可以用來做階段性的流程控制。

import java.util.concurrent.*;

public class Test {

    public static void main(String[] args) throws Exception {
        threadTest();
    }

    /**執行測試*/
    static void threadTest() throws Exception {
        var wp = new WeddingPhaser();
        Thread pst[] = new Thread[7];
        for(var i=0;i<5;++i) pst[i]=new Thread(new Person(wp,"賓客" + i));
        pst[5]=new Thread(new Person(wp,"新郎"));
        pst[6]=new Thread(new Person(wp,"新娘"));
        //所有線程都建立完成了纔可以開始執行,否則流程將錯亂(某些成員可能在第二階段及其後才註冊)
        for (Thread t : pst) t.start();
    }

    /**創建自己的Phaser並指定某一階段執行什麼任務*/
    static class WeddingPhaser extends Phaser {
        //每一次到達階段時將執行的任務,返回true時表示所有階段完成
        @Override
        protected boolean onAdvance(int phase, int registeredParties) {
            switch (phase) {
                case 0:
                    System.out.println("\n階段"+phase+"所有人到齊,共"+registeredParties);
                    return false;
                case 1:
                    System.out.println("\n階段"+phase+"所有人用餐完畢,共"+registeredParties);
                    return false;
                case 2:
                    System.out.println("\n階段"+phase+"所有賓客離場,共"+registeredParties);
                    return false;
                case 3:
                    System.out.println("\n階段"+phase+"新郎新娘洞房,共"+registeredParties);
                    return true;
                default:
                    return super.onAdvance(phase,registeredParties);
            }
        }
        private void phase0(String name) throws Exception{
            System.out.print(name+"到場 ");
            Thread.sleep(1000);
            this.arriveAndAwaitAdvance();
        }
        private void phase1(String name) throws Exception{
            System.out.print(name+"用餐 ");
            Thread.sleep(1000);
            this.arriveAndAwaitAdvance();
        }
        private void phase2(String name) throws Exception{
            if(name.startsWith("賓客")){
                //所有賓客在此階段離場
                System.out.print(name+"離場 ");
                Thread.sleep(1000);
                //到達最後階段,註銷自己
                this.arriveAndDeregister();
            }else{
                //新郎新娘送客但不離場
                System.out.print(name+"送客 ");
                Thread.sleep(1000);
                this.arriveAndAwaitAdvance();
            }
        }
        private void phase3(String name) throws Exception{
            if(name.startsWith("新")){
                System.out.print(name+"洞房 ");
                Thread.sleep(1000);
            }else{
                //賓客在上一階段就已經註銷了自己,但由於是異步的,因此這個階段可能還會有賓客線程未銷燬,依然執行
                System.out.print(name+"到家 ");
            }
            //階段全部結束,註銷自己
            this.arriveAndDeregister();
        }
        public void runPhase(String name){
            try {
                phase0(name);
                phase1(name);
                phase2(name);
                phase3(name);
            } catch (Exception e){}
        }
    }

    /**參與Phaser的成員*/
    static class Person implements Runnable {
        final WeddingPhaser wp;
        final String name;
        Person(WeddingPhaser wp,String name){
            this.wp = wp;
            this.name = name;
            this.wp.register();
        }
        @Override
        public void run() {
            wp.runPhase(name);
        }
    }
}
/*控制檯輸出:
賓客1到場 新郎到場 賓客3到場 賓客4到場 賓客2到場 賓客0到場 新娘到場 
階段0所有人到齊,共7
新娘用餐 賓客3用餐 賓客1用餐 賓客0用餐 賓客4用餐 新郎用餐 賓客2用餐 
階段1所有人用餐完畢,共7
新郎送客 賓客0離場 新娘送客 賓客4離場 賓客1離場 賓客3離場 賓客2離場 賓客4到家 賓客0到家 
階段2所有賓客離場,共2
賓客3到家 新娘洞房 新郎洞房 
階段3新郎新娘洞房,共0
*/

5.6. Semaphore

一個可以反覆使用的,限制最大活躍線程數量的便捷工具類,常用來做限流.

當信號量爲0時不再有線程可以獲取到信號量,直到有其他線程放棄信號量.

var s = new Semaphore(1);//同時只能活躍1個線程
for(var i=0;i<2;++i){
    var iF = i;
    var t = new Thread(()->{
        for(var j=0;j<9;++j){
            try {
                s.acquire();//獲得信號量,沒獲得信號量的線程將等待
                Thread.sleep(100);
                System.out.println("t"+iF+" is running");
            } catch (Exception e) {e.printStackTrace();} finally {
                s.release();//釋放信號量
            }
        }
    });
    t.start();
    System.out.println(t.getState());
}

5.7. Exchanger

簡單易用的線程通信工具類,交換兩個線程的數據,每當交換完成後就可以重用,如交易雙方,一手交錢一手交貨才完成交換.

var ec = new Exchanger<String>();
for(var i=0;i<2;++i){
    var iF = i;
    new Thread(()->{
        String s = "t"+iF, s1 = "T"+iF;
        try {
            //交換數據,阻塞線程直到雙方都調用了這個方法
            s = ec.exchange(s);
            System.out.println("t"+iF+" var:"+s);
            s1 = ec.exchange(s1);
            System.out.println("t"+iF+" var1:"+s1);
        } catch (InterruptedException e) {e.printStackTrace();}
    }).start();
}
Thread.sleep(100);
/*控制檯輸出:
t0 var:t1
t1 var:t0
t0 var1:T1
t1 var1:T0
*/

5.8. LockSupport

一個用來支撐自定義鎖的工具類,也可以直接使用使當前線程等待

var t = new Thread(()->{
    LockSupport.park();//使自己進入等待, 如果持有鎖,不會自動釋放鎖
    System.out.println("等待結束");
    System.out.println(new Date().getTime());
});
t.start();
System.out.println(new Date().getTime());
Thread.sleep(1000);
//使線程t允許運行,
//如果unpark先於park那麼該次park將會直接跳過,即該次park不會造成線程等待
LockSupport.unpark(t);
Thread.sleep(100);
/*控制檯輸出:
1616410337402
等待結束
1616410338411
*/

5.9. AbstractQueuedSynchronizer

AQS是鎖的核心.

AQS的核心是一個volatile int類型的變量state,以及監控它的一個雙向鏈表,鏈表的每個Node裝的線程.

每當線程嘗試獲得鎖時,先檢查state是否爲0,爲0則爭搶鎖,大於0時如果當前線程已經獲得鎖則重入否則嘗試獲得鎖失敗,進入鏈表尾部(CAS)等待獲得鎖.

6. 併發容器

6.1. 阻塞隊列

SynchronousQueue

//容量爲0的阻塞隊列,用於線程間交換數據,直接將數據送往另一個線程
public static SynchronousQueue synchronousQueue = new SynchronousQueue();
public static void main(String[] args) throws Exception{
    new Thread(()->{
        try {
            //取出
            System.out.println(synchronousQueue.take());
            System.out.println(synchronousQueue.take());
            System.out.println("OVER2");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();
    //阻塞等待索取
    synchronousQueue.put("synchronousQueue2");
    synchronousQueue.put("synchronousQueue1");
    System.out.println("OVER1");
}

TransferQueue

//提供獨特的transfer方法,阻塞當前線程,直到被transfer放入的數據被取出
public static TransferQueue transferQueue = new LinkedTransferQueue();
public static void main(String[] args) throws Exception{
    for (int i = 0; i < 2; i++) {
        new Thread(()->{
            try {
                //取出數據
                System.out.println(transferQueue.take());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
    //阻塞到被取出
    transferQueue.transfer("transferQueue1");
    System.out.println("transferQueue1over");
    transferQueue.transfer("transferQueue2");
    System.out.println("transferQueue2over");
}

DelayQueue

//延時隊列
static class MyDelayed implements Delayed{
    String msg;
    long time;
    public MyDelayed(String msg, long time) {
        this.msg = msg;
        this.time = time+System.currentTimeMillis();
    }
    @Override //到達執行時間的剩餘時間
    public long getDelay(TimeUnit unit) {
        return unit.convert(time-System.currentTimeMillis(),TimeUnit.MILLISECONDS);
    }
    @Override //確定優先級
    public int compareTo(Delayed o) {
        return (int)(getDelay(TimeUnit.MILLISECONDS)-o.getDelay(TimeUnit.MILLISECONDS));
    }
}
public static DelayQueue<MyDelayed> delayQueue = new DelayQueue();
public static void main(String[] args) throws Exception{
    delayQueue.add(new MyDelayed("asd3",2000l));
    delayQueue.add(new MyDelayed("asd1",1000l));
    delayQueue.add(new MyDelayed("asd2",1500l));
    MyDelayed myDelayed;
    while ((myDelayed=delayQueue.take())!=null)
        System.out.println(myDelayed.msg+" ,current: "+System.currentTimeMillis());
}

ArrayBlockingQueue和LinkedBlockingQueue

用於在線程間傳遞任務

static void threadTest() throws Exception {
    var q = new LinkedBlockingQueue<String>(4);
    var t1 = new Thread(()->{
        for(var i=0;i<100;++i){
            try {
                q.put("s"+i);
                if(i==99)q.put("over");
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    });
    var t2 = new Thread(()->{
        for(;;){
            try {
                var s = q.poll();
                System.out.println(s);
                if(s!=null&&s.equals("over"))return;
                Thread.sleep(51);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
}

6.2. 其他併發相關容器

HashTable和HashMap

Hashtable是一個同步的Map容器,它使用synchronized關鍵字做同步,這個設計在高併發下性能低下,基本不再使用.

HashtableHashMap最大的區別是HashMap完全不加鎖.

通過Collections.synchronizedMap(new HashMap<>())HashMap變爲加鎖的版本.

Vector和ArrayList

Vector是一個List容器,它使用synchronized關鍵字做同步,這個設計在高併發下性能低下,基本不再使用.

VectorArrayList最大的區別是ArrayList完全不加鎖.

通過Collections.synchronizedList(new ArrayList<>())ArrayList變爲加鎖的版本.

ConcurrentHashMap

高性能併發Map,內部採用了CAS

var map = new  ConcurrentHashMap<String,String>();
map.put("a","A");
System.out.println(map.get("a"));

ConcurrentSkipListMap

高性能併發Map,內部採用了CAS,和ConcurrentHashMap相比,ConcurrentSkipListMap是有序的,且便利速度明顯快於ConcurrentHashMap

var map = new ConcurrentSkipListMap<String,String>();
map.put("a","A");
System.out.println(map.get("a"));

ConcurrentLinkedQueue

高性能併發Queue,內部採用了CAS

var tickets = new  ConcurrentLinkedQueue<String>();
for(var i=0;i<100;++i){
    tickets.add("ticket"+i);
}
var ts = new Thread[3];
for(var i=0;i<3;++i){
    ts[i] = new Thread(()->{
        for(;;){
            var tk = tickets.poll();//取出,如果使用ArrayList,下面則可能輸出空
            if(tk!=null) System.out.println(tk);
            else return;
        }
    });
}
for (Thread t : ts) t.start();
for (Thread t : ts) t.join();

CopyOnWriteList

寫時複製List.

讀取數據時不加鎖,寫數據時加鎖,先拷貝原內存到一個新的內存,並添加一個位置存儲新的數據,寫完後將指向原內存的引用指向新內存.

典型的以空間換時間.

在寫少讀多的場景下性能極高.

var l = new CopyOnWriteArrayList<String>();
l.add("asd");
System.out.println(l.get(0));

7. 線程併發

併發(concurrent)和並行(parallel)的關係:併發是指任務提交(不一定並行),並行是指任務執行,並行是併發的子集.

7.1. 線程池

ThreadPoolExecutor

一個持有一定量線程的池子,併發情況下避免頻繁創建銷燬線程,並可以提供定製化拒絕策略.

一般不使用默認線程池,他們各自有着缺陷.

常用的定時任務線程池quartz.

//完整構造線程池
public ThreadPoolExecutor(int corePoolSize, //核心線程數(核心線程不會被銷燬)
                          int maximumPoolSize, //最大線程數
                          long keepAliveTime, //超過核心線程數的線程的最大空閒生存時間,其後將可能被銷燬
                          TimeUnit unit, //keepAliveTime的單位
                          BlockingQueue<Runnable> workQueue, //線程隊列,當線程數超過核心線程數時入隊
                          ThreadFactory threadFactory, //線程工廠
                          RejectedExecutionHandler handler) //當線程數滿,隊列滿時的拒絕策略
{
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
ThreadPoolExecutor::execute執行的三個步驟
//ThreadPoolExecutor::execute
public void execute(Runnable command) {
	if (command == null)
		throw new NullPointerException();
	/*
         * 分三步進行:
         * 1. 如果運行的線程少於corePoolSize,
         * 嘗試以command作爲第一個task開啓一個一個新核心線程.
         * 2. 如果成功將command入隊workQueue,
         * 雙重檢測確保線程池正RUNNING,
         * (可能有其他線程執行了shutdown).
         * 如果線程池已經shutdown,則回滾入隊操作,
         * 並執行拒絕策略
         * 3. 如果無法入隊,直接添加新的工作線程並執行command,
         * 如果操作失敗了,則說明線程池可能已經shutdown或飽和了,
         * 則執行拒絕策略
         */
	//獲取ctl快照
	int c = ctl.get();
	//第一步
	//判斷工作線程數是否少於設定的核心線程數值
	if (workerCountOf(c) < corePoolSize) {
		//添加核心工作線程
		if (addWorker(command, true))
			return;
		//重新獲取ctl快照(ctl可能已被其他線程修改)
		c = ctl.get();
	}
	//第二部
	//如果線程池正RUNNING,將command加入workQueue
	if (isRunning(c) && workQueue.offer(command)) {
		//重新獲取ctl快照
		int recheck = ctl.get();
		//雙重檢測,確保線程池沒有shutdown,如果shutdown了則將command出隊workQueue
		if (! isRunning(recheck) && remove(command))
			//執行拒絕策略
			reject(command);
		//判斷此時線程池正RUNNING,且可用工作線程爲0,
		else if (workerCountOf(recheck) == 0)
			//添加新的非核心線程,並從workQueue中取出首個command運行
			addWorker(null, false);
	}
	//隊列可能已滿從而失敗的情況下,直接添加非核心工作線程,並將command作爲task運行
	else if (!addWorker(command, false))
		//執行addWorker失敗(線程池關閉或飽和)則執行拒絕策略
		reject(command);
}

線程池中線程的5種狀態:RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED.

ForkJoinPool

  • 分解並彙總任務.
  • 用很少線程執行很多任務(子任務),而ThreadPoolExecutor做不到子任務.
  • CPU密集型線程池.
  • 使用Executors::newWorkStealingPool可以便捷地創建FJP線程池.
  • jdk8提供的流式API中Collection::parallelStream(並行流)內部就是使用的FJP.
static void testFJP() throws Exception {
    //實現一個RecursiveTask,如果實現RecursiveAction則是沒有返回值的任務
    class AddTask extends RecursiveTask<Long> {
        public final int start,end;
        public final int MAX_NUM = 1000;
        public AddTask(int start, int end) {
            this.start = start;this.end = end;
        }
        @Override
        protected Long compute() {
            var len = end-start;
            if(len<=MAX_NUM){
                var sum = 0L;
                for(var i=start;i<end;++i) sum+=i;
                return sum;
            }else{
                var middle = start+len/2;
                var t1 = new AddTask(start,middle);
                var t2 = new AddTask(middle,end);
                t1.fork();t2.fork();//fork分出子任務
                return t1.join()+t2.join();//將子任務join,當前線程等待子任務完成任務
            }
        }
    }
    //使用FJP執行計算密集任務,充分發揮CPU性能
    System.out.println(new ForkJoinPool().submit(new AddTask(0, 9999)).get());
}

7.2. ThreadLocal

ThreadLocal可以在當前線程存儲一個變量,因此ThreadLocal具有線程隔離的效果.

其內部維護了一個ThreadLocalMap,key爲線程,value爲set設置的值.

如spring的聲明式事務,就是將數據庫連接存入ThreadLocal,以保證在同一個線程內的操作是屬於同一個連接,從而實現事務.

var s = new ThreadLocal<String>();
s.set("asd");
System.out.println(s.get());

7.3. 強軟弱虛引用

1. 強引用

普通的引用都是強引用.

var o = new Object()就創建了一個強引用.

強引用只有其不再被使用時,其內存纔會被回收.

class MyObj {
	@Override //被回收前執行的方法
    protected void finalize() throws Throwable {System.out.println("我沒了");}
}
var m = new MyObj();
System.gc();//第一次GC,m指向的對象不會被回收
Thread.sleep(1111);
System.out.println("睡了1111");
m=null;//使m指向的對象不再被使用
System.gc();//第二次GC,回收m指向的對象
System.exit(0);
/*控制檯輸出:
睡了1111
我沒了
*/

2. 軟引用

當內存不夠時,即時該變量依然被引用還是會被回收.

創建軟引用需要使用對象SoftReference.

軟引用可以用來做緩存.

//測試前需要將堆內存設置小一點,如 -Xms:20M -Xmx:20M
var s = new SoftReference<byte[]>(new byte[1024*1024*10]);
System.out.println(s.get());
System.gc();//不回收
System.out.println(s.get());
Thread.sleep(1000);
System.out.println("睡了1000");
System.gc();//不回收
var y = new byte[1024*1024*15];//填充內存,此時內存已不足以放下y,軟引用將被回收以放下y
System.out.println(s.get());//輸出null
System.exit(0);

3. 弱引用

只要發生gc,弱引用將被回收.

創建弱引用需要使用對象WeakReference.

一般用在容器中,只要該對象的強引用不存在了,該對象就可以被回收了.

如ThreadLocal內部的Entry就是使用的WeakReference,即ThreadLocal持有某個變量時不影響其被回收,避免了內存泄漏.

var s = new WeakReference<byte[]>(new byte[1024]);
System.out.println(s.get());
System.gc();//只要發生gc,弱引用將被回收
System.out.println(s.get());//輸出null
System.exit(0);

4. 虛引用

只要發生gc,虛引用指向的對象一定被回收,同時向queue中發送通知.

無法通過虛引用對象拿到其內的對象,它的唯一作用是被回收時發送一個通知.

創建虛引用需要使用對象PhantomReference.

主要用在JVM中,和普通程序員無關.

如:當使用堆外內存時,使用該堆外內存的對象被回收時發送一個通知,通知某一個對象對該堆外對象進行處理.

class MyCls {
    protected void finalize() throws Throwable{System.out.println("It is over.");}
}
var r = new ReferenceQueue<MyCls>();
var p = new PhantomReference<MyCls>(new MyCls(),r);
System.out.println(p.get());//get始終爲null
var t = new Thread(()->{
    for(;;){
        try {Thread.sleep(100);} catch (InterruptedException e) {}
        var s = r.poll();
        if (s!=null) {
            System.out.println("回收");
            return;
        }
        System.gc();//gc將回收p內對象,併發送消息
    }
});
t.start();
t.join();
/*控制檯輸出:
null
It is over.
回收
*/

7.4. VarHandle

jdk9提供的java.lang.invoke.VarHandle,是一個指向變量的引用,可以對對象普通屬性進行原子操作,比反射快(無需運行時檢測,直接操作二進制碼).

VarHandle極大的提升了JUC下工具的效率,如使用VarHandle優化了AbstractQueuedSynchronizer更快的操作內部的鏈表.

public class XXX{
    long x = 100L;
    private static VarHandle varHandle;
    static {
        try {
            varHandle = MethodHandles.lookup().findVarHandle(XXX.class,"x",long.class);
        } catch (Exception e) { e.printStackTrace(); }
    }
    public static void main(String[] args) throws Exception {
        var me = new XXX();
        System.out.println("x="+varHandle.get(me));//100
        varHandle.set(me,1000l);
        System.out.println("x="+me.x);//1000
        varHandle.compareAndSet(me,100l,10000l);//CAS
        System.out.println("x="+me.x);//1000
    }
}

7.5. ExecutorService和Callable及FutureTask

ThreadPoolExecutor實現了ExecutorService接口

Callable是一個允許有返回值的任務

Callable一般配合ExecutorService使用,異步執行任務,在另一個線程同步等待結果.

常用在異步執行多個任務,在主線程阻塞等待所有異步任務返回結果.

Callable<String> callable = ()->"返回值";
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> submit = executorService.submit(callable);//異步執行任務
System.out.println(submit.get());//阻塞,直到獲得返回值

FutureTask是一個簡化的版本,既是一個Task,也是一個Future.

var futureTask = new FutureTask<String>(()->"返回了一個值");
new Thread(futureTask).start();
System.out.println(futureTask.get());//阻塞

7.5. CompletableFuture

JDK8提供便捷的函數式(鏈式)異步工具類,內部使用了ForkJoinPool.

期待JDK提供asyncawait關鍵字更方便的實現異步編程!

常用方法:

CompletableFuture::runAsync:異步執行一個Runnable.

CompletableFuture.runAsync(()-> System.out.print("異步任務."));
System.out.print("同步任務.");
//控制檯輸出: 同步任務.異步任務.

CompletableFuture::supplyAsync:異步執行一個有返回值的任務

CompletableFuture::thenAcceptAsync:接收上一個CompletableFuture的返回值,並異步執行任務,thenAccept是其同步版本.

CompletableFuture.supplyAsync(()->"異步任務返回").thenAcceptAsync(System.out::println);

CompletableFuture::anyOf:任意一個任務完成執行接下來的任務.

CompletableFuture.anyOf(
    CompletableFuture.runAsync(()-> {
        try {Thread.sleep(100);} catch (InterruptedException e) {}
        System.out.print("執行任務1.");
    }),
    CompletableFuture.runAsync(()-> System.out.print("執行任務2."))
).thenRun(()-> System.out.print("執行完成."));
//控制檯輸出: 執行任務2.執行完成.執行任務1.

CompletableFuture::allOf:所有任務完成執行接下來的任務,用法同anyOf.

CompletableFuture.allOf(
    CompletableFuture.supplyAsync(()->"ret1").thenAccept(System.out::println),
    CompletableFuture.runAsync(()-> System.out.println("run2"))
).join();//使當前線程阻塞,直到上面兩個任務都完成

CompletableFuture::delayedExecutor返回一個定時執行任務的Executor.

CompletableFuture.delayedExecutor(1,TimeUnit.SECONDS).execute(()-> System.out.println("1秒後"));

8. 練習題

8.1. 線程交替運行

兩個線程分別交替打印1-25和A-Z.

var lock = new Object();
var t1 = new Thread(()->{
    for(var i='A';i<='Z';++i){
        synchronized (lock){
            lock.notify();
            System.out.println("字母"+i);
            try {
                if(i!='Z')lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
});
var t2 = new Thread(()->{
    for(var i=0;i<26;++i){
        synchronized (lock) {
            lock.notify();
            System.out.println("數字"+i);
            try {
                if(i!=25)lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
});
t1.start();
t2.start();
t1.join();
t2.join();

8.2. 實現阻塞隊列

簡單實現阻塞隊列,用以支撐生產者消費者模型

//阻塞隊列
static class SyncQueue<T> {
    private final List<T> list;
    private final int cap;
    private volatile int size = 0;
    SyncQueue(int cap){
        list = new LinkedList();
        this.cap = cap;
    }
    public int getSize(){ return size; }
    public synchronized void put(T val){
        //使用while避免在被喚醒後即時已經到達cap也被放行
        while (list.size() >= cap) doWait();
        list.add(val);++size;
        this.notifyAll();
    }
    public synchronized T get(){
        while (list.size() <= 0) doWait();
        T val = list.remove(0);--size;
        this.notifyAll();
        return val;
    }
    private void doWait(){
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//生產者消費者模型
static void threadTest() throws Exception {
    var q = new SyncQueue<Integer>(10);
    var ts = new Thread[4];
    for(var i=0;i<4;++i){
        if(i%2!=0)
            ts[i]=new Thread(()->{
                for(var j=0;j<=20;++j){
                    var s = q.getSize();q.put(s);
                    System.out.println(Thread.currentThread().getName()+" put "+s);
                }
            });
        else
            ts[i]=new Thread(()->{
                for(var j=0;j<=20;++j) System.out.println(Thread.currentThread().getName()+" get "+q.get());
            });
    }
    for (Thread t : ts) t.start();
    for (Thread t : ts) t.join();
}

分離阻塞及喚醒消費者生產者的阻塞隊列

class SyncQueue<T> {
    private final List<T> list;
    private final int cap;
    private volatile int size = 0;
    private final ReentrantLock lock;
    //使用Condition分離put和get
    private final Condition conditionPut,conditionSet;
    SyncQueue(int cap){
        list = new LinkedList();
        this.cap = cap;
        lock = new ReentrantLock();
        conditionPut = lock.newCondition();
        conditionSet = lock.newCondition();
    }
    public int getSize(){ return size; }
    public void put(T val){
        try {
            lock.lock();
            while (list.size() >= cap) conditionPut.await();
            list.add(val);++size;
            conditionSet.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public T get(){
        try {
            lock.lock();
            while (list.size() <= 0) conditionSet.await();
            T val = list.remove(0);--size;
            conditionPut.signalAll();
            return val;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return null;
        } finally {
            lock.unlock();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章