Ty-JUC基礎筆記

Ty_0026發表於2020-12-18

JUC

簡介

在 Java 5.0 提供了 java.util.concurrent (簡稱 JUC )包,在此包中增加了在併發程式設計中很常用 的實用工具類,用於定義類似於執行緒的自定義子 系統,包括執行緒池、非同步 IO 和輕量級任務框架。 提供可調的、靈活的執行緒池。還提供了設計用於 多執行緒上下文中的 Collection 實現等。

volatile 關鍵字與記憶體可見性

  • 記憶體可見性

    • 記憶體可見性(Memory Visibility)是指當某個執行緒正在使用物件狀態 而另一個執行緒在同時修改該狀態,需要確保當一個執行緒修改了物件 狀態後,其他執行緒能夠看到發生的狀態變化。
    • 可見性錯誤是指當讀操作與寫操作在不同的執行緒中執行時,我們無 法確保執行讀操作的執行緒能適時地看到其他執行緒寫入的值,有時甚 至是根本不可能的事情。
    • 我們可以通過同步來保證物件被安全地釋出。除此之外我們也可以 使用一種更加輕量級的 volatile 變數。
    //測試程式碼
    public class TestVolatile {
    	
    	public static void main(String[] args) {
    		ThreadDemo td = new ThreadDemo();
    		new Thread(td).start();
    		
    		while(true){
    			if(td.isFlag()){
    				System.out.println("------------------");
    				break;
    			}
    		}
    	}
    }
    
    class ThreadDemo implements Runnable {
    	//不加volatile時與加volatile時對比
    	private volatile boolean flag = false;
    
    	@Override
    	public void run() {
    		
    		try {
    			Thread.sleep(200);
    		} catch (InterruptedException e) {
            }
    		flag = true;
    		System.out.println("flag=" + isFlag());
    	}
    
    	public boolean isFlag() {
    		return flag;
    	}
    
    	public void setFlag(boolean flag) {
    		this.flag = flag;
    	}
    
    }
    
  • volatile 關鍵字

    • Java 提供了一種稍弱的同步機制,即 volatile變數,用來確保將變數的更新操作通知到其他執行緒。 可以將 volatile 看做一個輕量級的鎖,但是又與鎖有些不同:
      • 對於多執行緒,不是一種互斥關係
      • 不能保證變數狀態的"原子性操作"

原子變數與CAS演算法

  • CAS演算法
    • CAS (Compare-And-Swap) 是一種硬體對併發的支援,針對多處理器 操作而設計的處理器中的一種特殊指令,用於管理對共享資料的並 發訪問。
    • CAS 是一種無鎖的非阻塞演算法的實現。
    • CAS 包含了 3 個運算元:
      • 需要讀寫的記憶體值 V
      • 進行比較的值 A
      • 擬寫入的新值 B
    • 當且僅當 V 的值等於 A 時,CAS 通過原子方式用新值 B 來更新 V 的 值,否則不會執行更新操作。重新嘗試進行賦值再次讀取VAB等值
  • 原子變數
    • 類的小工具包,支援在單個變數上解除鎖的執行緒安全程式設計。事實上,此包中的類可 將 volatile 值、欄位和陣列元素的概念擴充套件到那些也提供原子條件更新操作的類。
    • 類 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的例項各自提供對 相應型別單個變數的訪問和更新。每個類也為該型別提供適當的實用工具方法。
    • AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 類進一步擴充套件了原子操 作,對這些型別的陣列提供了支援。這些類在為其陣列元素提供 volatile 訪問語義方 面也引人注目,這對於普通陣列來說是不受支援的。
    • 核心方法:boolean compareAndSet(expectedValue, updateValue)
    • java.util.concurrent.atomic 包下提供了一些原子操作的常用類:
      • AtomicBoolean 、AtomicInteger 、AtomicLong 、 AtomicReference
      • AtomicIntegerArray 、AtomicLongArray
      • AtomicMarkableReference
      • AtomicReferenceArray
      • AtomicStampedReference

Automic等類實質上就是在底層儲存變數上新增了volatile關鍵字,保證了記憶體可見性,使用CAS演算法保證原子性

image-20201218094400753

image-20201218094413318

ConcurrentHashMap 鎖分段機制

  • Java 5.0 在 java.util.concurrent 包中提供了多種併發容器類來改進同步容器 的效能。

  • ConcurrentHashMap 同步容器類是Java 5 增加的一個執行緒安全的雜湊表。對 與多執行緒的操作,介於 HashMap 與 Hashtable 之間。內部採用“鎖分段” 機制替代 Hashtable 的獨佔鎖。進而提高效能。

  • 此包還提供了設計用於多執行緒上下文中的 Collection 實現: ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、 CopyOnWriteArrayList 和 CopyOnWriteArraySet。當期望許多執行緒訪問一個給 定 collection 時,ConcurrentHashMap 通常優於同步的 HashMap, ConcurrentSkipListMap 通常優於同步的 TreeMap。當期望的讀數和遍歷遠遠 大於列表的更新數時,CopyOnWriteArrayList 優於同步的 ArrayList

  • 多執行緒時如果存在共享資料,使用上述類是優於使用ArrayList等集合的,但是如果不存在多執行緒的情況,或者多執行緒不涉及共享資料,使用ArrayList等優於上述類

  • JDK1.7及之前的 ConcurrentHashMap 使用 鎖分段機制 實現,JDK1.8則使用 陣列+連結串列+紅黑樹資料結構CAS原子操作實現。

    • JDK 1.5 - 1.7

    image-20201218101130963

    ConcurrentHashMap找那個定義了一個Segment<K, V>[]陣列(長度為16)來講Hash表分段儲存,從而實現分段加鎖;而每個Segment元素則與HashMap結構類似,其包含了一個HashEntry陣列,用來儲存Key/Value對。Segment繼承了ReetrantLock,表示Segment是一個可重入鎖,

    • JDK 1.8

    JDK 1.8 的ConcurrentHashMap的資料結構比JDK 1.7 之前的要簡單的多,其使用的是HashMap一樣的資料結構:陣列+連結串列+紅黑樹。ConcurrentHashMap 中包含一個table陣列,其型別是一個Node陣列;而Node陣列是一個繼承自Map.Entry<K,V> 的連結串列,而當這個連結串列結構中的陣列大於8,則將資料結構升級為TreeBin型別的紅黑樹結構。另外,JDK 1.8 的ConcurrentHashMap中還包含一個重要的屬性,sizeCtl,它是一個控制識別符號,不同的值代表不同的意思:為0時,表示hash還沒初始化;為正數時表示初始化或下一次擴容的大小,相當於一個閾值。即:如果hash表的實際大小 >= sizeCtl,則進行擴容,預設情況下為當前ConcurrentHashMap容量的0.75倍;而如果sizeCtl 為 -1,表示正在進行初始化操作。為 -N 時,則表示有 N-1 個執行緒正在進行擴容。

CountDownLatch 閉鎖

  • Java 5.0 在 java.util.concurrent 包中提供了多種併發容器類來改進同步容器 的效能。
  • CountDownLatch 一個同步輔助類,在完成一組正在其他執行緒中執行的操作 之前,它允許一個或多個執行緒一直等待。
  • 閉鎖可以延遲執行緒的進度直到其到達終止狀態,閉鎖可以用來確保某些活動直到其他活動都完成才繼續執行:
    • 確保某個計算在其需要的所有資源都被初始化之後才繼續執行;
    • 確保某個服務在其依賴的所有其他服務都已經啟動之後才啟動;
    • 等待直到某個操作所有參與者都準備就緒再繼續執行。
public class JUCTest {
    public static void main(String[] args) {
        //初始10個執行緒
        CountDownLatch  latch=new CountDownLatch(10);
        LatchDemo latchDemo = new LatchDemo(latch);
        long l = System.currentTimeMillis();
        for (int i = 0; i < 10; i++) {
            new Thread(latchDemo).start();
        }
        //等待執行緒執行完畢
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long currentTimeMillis = System.currentTimeMillis();
        System.out.println("執行時間:"+(currentTimeMillis-l));
    }
}

class LatchDemo implements Runnable{

    private CountDownLatch countDownLatch;

    public LatchDemo(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        synchronized (this){
            try {
                for (int i = 0; i <50000; i++) {
                    System.out.println(i);
                }
            } finally {
                //執行緒每結束一個就減一
                countDownLatch.countDown();
            }
        }
    }
}

Callable 介面

  • java 5.0 在 java.util.concurrent 提供了一個新的建立執行 執行緒的方式:Callable 介面
  • Callable 介面類似於 Runnable,兩者都是為那些其例項可 能被另一個執行緒執行的類設計的。但是 Runnable 不會返 回結果,並且無法丟擲經過檢查的異常。
  • Callable 需要依賴FutureTask ,FutureTask 也可以用作閉鎖。
  • 具體實現在java-執行緒筆記

Lock 同步鎖

  • 在 Java 5.0 之前,協調共享物件的訪問時可以使用的機 制只有 synchronized 和 volatile 。Java 5.0 後增加了一些 新的機制,但並不是一種替代內建鎖的方法,而是當內 置鎖不適用時,作為一種可選擇的高階功能。
  • ReentrantLock 實現了 Lock 介面,並提供了與 synchronized 相同的互斥性和記憶體可見性。但相較於 synchronized 提供了更高的處理鎖的靈活性。

Condition 控制執行緒通訊

  • Condition 介面描述了可能會與鎖有關聯的條件變數。這些變數在用 法上與使用 Object.wait 訪問的隱式監視器類似,但提供了更強大的 功能。需要特別指出的是,單個 Lock 可能與多個 Condition 物件關 聯。為了避免相容性問題,Condition 方法的名稱與對應的 Object 版 本中的不同。
  • 在 Condition 物件中,與 wait、notify 和 notifyAll 方法對應的分別是 await、signal 和 signalAll。
  • Condition 例項實質上被繫結到一個鎖上。要為特定 Lock 例項獲得 Condition 例項,請使用其 newCondition() 方法。

ReadWriteLock 讀寫鎖

  • ReadWriteLock 維護了一對相關的鎖,一個用於只讀操作, 另一個用於寫入操作。只要沒有 writer,讀取鎖可以由 多個 reader 執行緒同時保持。寫入鎖是獨佔的。
  • ReadWriteLock 讀取操作通常不會改變共享資源,但執行 寫入操作時,必須獨佔方式來獲取鎖。對於讀取操作佔 多數的資料結構。 ReadWriteLock 能提供比獨佔鎖更高 的併發性。而對於只讀的資料結構,其中包含的不變性 可以完全不需要考慮加鎖操作。
@Test
public void testRWLock(){
    ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
    new Thread(() -> readWriteLockDemo.setNumber((int) (Math.random()*101)),"write").start();
    for (int i = 0; i < 100; i++) {
        new Thread(() -> {
            int number = readWriteLockDemo.getNumber();
        },"read"+i).start();
    }
}
class ReadWriteLockDemo{
    private int number=0;

    private ReadWriteLock lock=new ReentrantReadWriteLock();

    public int getNumber(){
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"Read"+number);
        } finally {
            lock.readLock().unlock();
        }
        return number;
    }

    public void setNumber(int value){
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"Write");
            number=value;
        } finally {
            lock.readLock().unlock();
        }
    }
}

synchronized

  • 一個物件裡面如果有多個synchronized方法,某一個時刻內,只要一個執行緒去呼叫 其中的一個synchronized方法了,其它的執行緒都只能等待,換句話說,某一個時刻 內,只能有唯一一個執行緒去訪問這些synchronized方法
  • 鎖的是當前物件this,被鎖定後,其它的執行緒都不能進入到當前物件的其它的 synchronized方法
  • 所有的非靜態同步方法用的都是同一把鎖——例項物件本身,也就是說如果一個實 例物件的非靜態同步方法獲取鎖後,該例項物件的其他非靜態同步方法必須等待獲 取鎖的方法釋放鎖後才能獲取鎖,可是別的例項物件的非靜態同步方法因為跟該實 例物件的非靜態同步方法用的是不同的鎖,所以毋須等待該例項物件已獲取鎖的非 靜態同步方法釋放鎖就可以獲取他們自己的鎖。
  • 所有的靜態同步方法用的也是同一把鎖——類物件本身,這兩把鎖是兩個不同的對 象,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的。但是一旦一個 靜態同步方法獲取鎖後,其他的靜態同步方法都必須等待該方法釋放鎖後才能獲取 鎖,而不管是同一個例項物件的靜態同步方法之間,還是不同的例項物件的靜態同 步方法之間,只要它們同一個類的例項物件!

執行緒池

執行緒池、執行緒排程見java-執行緒筆記

  • 第四種獲取執行緒的方法:執行緒池,一個 ExecutorService,它使用可能的幾個池執行緒之 一執行每個提交的任務,通常使用 Executors 工廠方法配置。
  • 執行緒池可以解決兩個不同問題:由於減少了每個任務呼叫的開銷,它們通常可以在 執行大量非同步任務時提供增強的效能,並且還可以提供繫結和管理資源(包括執行 任務集時使用的執行緒)的方法。每個 ThreadPoolExecutor 還維護著一些基本的統計數 據,如完成的任務數
  • 為了便於跨大量上下文使用,此類提供了很多可調整的引數和擴充套件鉤子 (hook)。但 是,強烈建議程式設計師使用較為方便的 Executors 工廠方法 :
    • Executors.newCachedThreadPool()(無界執行緒池,可以進行自動執行緒回收)
    • Executors.newFixedThreadPool(int)(固定大小執行緒池)
    • Executors.newSingleThreadExecutor()(單個後臺執行緒) 它們均為大多數使用場景預定義了設定。

執行緒排程

ScheduledExecutorService

一個 ExecutorService,可安排在給定的延遲後執行或定期執行的命令。

ForkJoinPool 分支/合併框架 工作竊取

  • Fork/Join 框架:就是在必要的情況下,將一個大任務,進行拆分(fork)成 若干個小任務(拆到不可再拆時),再將一個個的小任務運算的結果進 行 join 彙總。

image-20201217163541083

  • Fork/Join 框架與執行緒池的區別
    • 採用 “工作竊取”模式(work-stealing): 當執行新的任務時它可以將其拆分分成更小的任務執行,並將小任務加 到執行緒佇列中,然後再從一個隨機執行緒的佇列中偷一個並把它放在自己的隊 列中。
    • 相對於一般的執行緒池實現,fork/join框架的優勢體現在對其中包含的任務 的處理方式上.在一般的執行緒池中,如果一個執行緒正在執行的任務由於某些 原因無法繼續執行,那麼該執行緒會處於等待狀態。而在fork/join框架實現中, 如果某個子問題由於等待另外一個子問題的完成而無法繼續執行。那麼處理 該子問題的執行緒會主動尋找其他尚未執行的子問題來執行.這種方式減少了 執行緒的等待時間,提高了效能。
@Test
public void testFJPool(){
    ForkJoinPool forkJoinPool = new ForkJoinPool();
    ForkJoinTask<Long> forkJoinTask = new ForkJoinPoolDemo(0L,1000000L);
    Long invoke = forkJoinPool.invoke(forkJoinTask);
    System.out.println(invoke);
}
class ForkJoinPoolDemo extends RecursiveTask<Long>{

    private static final long serialVersionUID = -7564528911238627614L;

    private long start;
    private long end;

    private static final long THRESHOLD=0L;

    public ForkJoinPoolDemo(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if (end-start<=THRESHOLD){
            long sum=0L;
            for (long i = start; i <=end; i++) {
                sum+=i;
            }
            return sum;
        }else{
            long mid=(start+end)/2;
            ForkJoinPoolDemo left =new ForkJoinPoolDemo(start,mid);
            //進行拆分,並壓入執行緒佇列
            left.fork();
            ForkJoinPoolDemo right =new ForkJoinPoolDemo(mid+1,end);
            //進行拆分,並壓入執行緒佇列
            right.fork();
            return left.join()+right.join();
        }
    }
}

參考

https://my.oschina.net/alexjava/blog/3082603

https://blog.csdn.net/bill_xiang_/article/details/81122044

相關文章