JUC 基礎內容概述

Concurrent Programming in Java 的做者 Doug Lea 編寫了一個極其優秀的、免費的併發實用程序包,它包括併發應用程序的鎖、互斥、隊列、線程池、輕量級任務、有效的併發集合、原子的算術操做和其它基本構件。咱們通常稱這個包爲 J.U.C。java

1. JUC概況

如下是Java JUC包的主體結構:
node

  • Atomic : AtomicInteger
  • Locks : Lock, Condition, ReadWriteLock
  • Collections : Queue, ConcurrentMap
  • Executer : Future, Callable, Executor
  • Tools : CountDownLatch, CyclicBarrier, Semaphore

2. 原子操做

多個線程執行一個操做時,其中任何一個線程要麼徹底執行完此操做,要麼沒有執行此操做的任何步驟,那麼這個操做就是原子的。出現緣由: synchronized的代價比較高。算法

如下以AtomicInteger爲例:緩存

  • int addAndGet(int delta):以原子方式將給定值與當前值相加。 實際上就是等於線程安全版本的i =i+delta操做。
  • boolean compareAndSet(int expect, int update):若是當前值 == 預期值,則以原子方式將該值設置爲給定的更新值。 若是成功就返回true,不然返回false,而且不修改原值。
  • int decrementAndGet():以原子方式將當前值減 1。 至關於線程安全版本的–i操做。
  • int getAndAdd(int delta):以原子方式將給定值與當前值相加。 至關於線程安全版本的t=i;i+=delta;return t;操做。
  • int getAndDecrement():以原子方式將當前值減 1。 至關於線程安全版本的i–操做。
  • int getAndIncrement():以原子方式將當前值加 1。 至關於線程安全版本的i++操做。
  • int getAndSet(int newValue):以原子方式設置爲給定值,並返回舊值。 至關於線程安全版本的t=i;i=newValue;return t;操做。
  • int incrementAndGet():以原子方式將當前值加 1。 至關於線程安全版本的++i操做。

3. 指令重排

你的程序並不能老是保證符合CPU處理的特性。安全

要程序的最終結果等同於它在嚴格的順序化環境下的結果,那麼指令的執行順序就可能與代碼的順序不一致。併發

多核CPU,大壓力下,兩個線程交替執行,x,y輸出結果不肯定。可能結果:app

1ide

2函數

3高併發

4

x = 0 , y = 1

x = 1 , y = 1

x = 1 , y = 0

x = 0 , y = 0

4. Happens-before法則:(Java 內存模型)

若是動做B要看到動做A的執行結果(不管A/B是否在同一個線程裏面執行),那麼A/B就須要知足happens-before關係。

Happens-before的幾個規則:

  • Program order rule:同一個線程中的每一個Action都happens-before於出如今其後的任何一個Action。
  • Monitor lock rule:對一個監視器的解鎖happens-before於每個後續對同一個監視器的加鎖。
  • Volatile variable rule:對volatile字段的寫入操做happens-before於每個後續的同一個字段的讀操做。
  • Thread start rule:Thread.start()的調用會happens-before於啓動線程裏面的動做。
  • Thread termination rule:Thread中的全部動做都happens-before於其餘線程檢查到此線程結束或者Thread.join()中返回或者Thread.isAlive()==false。
  • Interruption rule:一個線程A調用另外一個另外一個線程B的interrupt()都happens-before於線程A發現B被A中斷(B拋出異常或者A檢測到B的isInterrupted()或者interrupted())。
  • Finalizer rule:一個對象構造函數的結束happens-before與該對象的finalizer的開始
  • Transitivity:若是A動做happens-before於B動做,而B動做happens-before與C動做,那麼A動做happens-before於C動做。
    由於CPU是能夠不按咱們寫代碼的順序執行內存的存取過程的,也就是指令會亂序或並行運行, 只有上面的happens-before所規定的狀況下,才保證順序性。

JMM的特性:

多個CPU之間的緩存也不保證明時同步;
JMM不保證建立過程的原子性,讀寫併發時,可能看到不完整的對象。(so D-check)

volatile語義:

volatile實現了相似synchronized的語義,卻又沒有鎖機制。它確保對  volatile字段的更新以可預見的方式告知其餘的線程。

  1. Java 存儲模型不會對volatile指令的操做進行重排序:這個保證對volatile變量的操做時按照指令的出現順序執行的。
  2. volatile變量不會被緩存在寄存器中(只有擁有線程可見),每次老是從主存中讀取volatile變量的結果。

ps:volatile並不能保證線程安全的,也就是說volatile字段的操做不是原子性的,volatile變量只能保證可見性。

5. CAS操做

Compare and Swap

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

實現簡單的非阻塞算法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

private volatile int value; // 藉助volatile原語,保證線程間的數據是可見的

 

public final int get() {

     return value;

}

 

public final int incrementAndGet() {

     for (;;) {

         int current = get();

         int next = current + 1 ;

         if (compareAndSet(current, next))

             return next;

     } //Spin自旋等待直到返爲止置

}

整個J.U.C都是創建在CAS之上的,對於synchronized阻塞算法,J.U.C在性能上有了很大的提高。會出現所謂的「ABA」問題

6. Lock 鎖

Synchronized屬於獨佔鎖,高併發時性能不高,JDK5之後開始用JNI實現更高效的鎖操做。

Lock—->

ReentrantLock—->

ReentrantReadWriteLock.ReadLock / ReentrantReadWriteLock.writeLock

ReadWriteLock—-> ReentrantReadWriteLock

LockSupport

Condition

方法名稱 做用
void lock() 獲取鎖。若是鎖不可用,出於線程調度目的,將禁用當前線程,而且在得到鎖以前,該線程將一直處於休眠狀態。
void lockInterruptibly() throws InterruptedException; 若是當前線程未被中斷,則獲取鎖。若是鎖可用,則獲取鎖,並當即返回。
Condition newCondition(); 返回綁定到此 Lock 實例的新 Condition
實例
boolean tryLock(); 僅在調用時鎖爲空閒狀態才獲取該鎖
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 若是鎖在給定的等待時間內空閒,而且當前線程未被中斷,則獲取鎖
void unlock(); 釋放鎖

PS : 通常來講,獲取鎖和釋放鎖是成對兒的操做,這樣能夠避免死鎖和資源的浪費。

7. AQS

鎖機制實現的核心所在。AbstractQueuedSynchronizer是Lock/Executor實現的前提。

AQS實現:

基本的思想是表現爲一個同步器,AQS支持下面兩個操做:

acquire:

1

2

3

4

5

while (synchronization state does not allow acquire){

     enqueue current thread if not already queued;

     possibly block current thread;

}

dequeue current thread if it was queued;

release:

1

2

3

update synchronization state;

if (state may permit a blocked thread to acquire)

     unlock one or more queued threads;

要支持這兩個操做,須要實現的三個條件:

  • Atomically managing synchronization state(原子性操做同步器的狀態位)
  • Blocking and unblocking threads(阻塞和喚醒線程)
  • Maintaining queues(維護一個有序的隊列)

Atomically managing synchronization state

使用一個32位整數來描述狀態位:private volatile int state; 對其進行CAS操做,確保值的正確性。

Blocking and unblocking threads

JDK 5.0之後利用JNI在LockSupport類中實現了線程的阻塞和喚醒。

LockSupport.park() //在當前線程中調用,致使線程阻塞
LockSupport.park(Object)
LockSupport.unpark(Thread)

Maintaining queues

在AQS中採用CHL列表來解決有序的隊列的問題。(CHL= Craig, Landin, and Hagersten)

Node裏面是什麼結構?

WaitStatus –>節點的等待狀態,一個節點可能位於如下幾種狀態:

  • CANCELLED = 1: 節點操做由於超時或者對應的線程被interrupt。節點不該該不留在此狀態,一旦達到此狀態將從CHL隊列中踢出。
  • SIGNAL = -1: 節點的繼任節點是(或者將要成爲)BLOCKED狀態(例如經過LockSupport.park()操做),所以一個節點一旦被釋放(解鎖)或者取消就須要喚醒(LockSupport.unpack())它的繼任節點。
  • CONDITION = -2:代表節點對應的線程由於不知足一個條件(Condition)而被阻塞。
  • 0: 正常狀態,新生的非CONDITION節點都是此狀態。

非負值標識節點不須要被通知(喚醒)。
隊列管理操做:

入隊enqueue:

採用CAS操做,每次比較尾結點是否一致,而後插入的到尾結點中。

1

2

3

do {

     pred = tail;

} while ( !compareAndSet(pred,tail,node) );

出隊dequeue:

1

2

while (pred.status != RELEASED) ;

     head  = node;

加鎖操做:

1

2

3

4

5

public final void acquire( int arg) {

     if (!tryAcquire(arg)

         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

     selfInterrupt();

}

釋放操做:

1

2

3

4

5

6

7

8

9

public final boolean release( int arg) {

     if (tryRelease(arg)) {

         Node h = head;

         if (h != null && h.waitStatus != 0 )

             unparkSuccessor(h);

         return true ;

     }

     return false ;

}

The synchronizer framework provides a ConditionObject class for use by synchronizers that maintain exclusivesynchronization and conform to the Lock interface.     —— Doug Lea《 The java.util.concurrent Synchronizer Framework 》

如下是AQS隊列和Condition隊列的出入結點的示意圖,能夠經過這幾張圖看出線程結點在兩個隊列中的出入關係和條件。

相關文章
相關標籤/搜索