1. 程式人生 > 其它 >Java JUC(java.util.concurrent)上篇

Java JUC(java.util.concurrent)上篇

技術標籤:javajava併發程式設計

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

volatile 關鍵字
volatile 關鍵字: 當多個執行緒進行操作共享資料時,可以保證記憶體中的資料是可見的;
volatile 不具備"互斥性";
volatile 不能保證變數的"原子性";
相較於 synchronized

是一種較為輕量級的同步策略;

i++ 的原子性問題
i++的操作實際上分為三個步驟: “讀-改-寫”;
原子性: 就是"i++"的"讀-改-寫"是不可分割的三個步驟;
原子變數: JDK1.5 以後, java.util.concurrent.atomic包下,提供了常用的原子變數;
原子變數中的值,使用 volatile 修飾,保證了記憶體可見性;
CAS(Compare-And-Swap) 演算法保證資料的原子性;
在這裡插入圖片描述
Atomic : AtomicInteger
Locks : Lock, Condition, ReadWriteLock
Collections : Queue, ConcurrentMap

Executer : Future, Callable, Executor
Tools : CountDownLatch, CyclicBarrier, Semaphore

原子操作
多個執行緒執行一個操作時,其中任何一個執行緒要麼完全執行完此操作,要麼沒有執行此操作的任何步驟,那麼這個操作就是原子的。出現原因: 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操作。

指令重排
你的程式並不能總是保證符合CPU處理的特性。

要程式的最終結果等同於它在嚴格的順序化環境下的結果,那麼指令的執行順序就可能與程式碼的順序不一致。
CAS操作
Compare and Swap

CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。

實現簡單的非阻塞演算法:

privatevolatileintvalue;// 藉助volatile原語,保證執行緒間的資料是可見的
 
publicfinalintget() {
    returnvalue;
}
publicfinalintincrementAndGet() {
    for(;;) {
        intcurrent = get();
        intnext = current +1;
        if(compareAndSet(current, next))
            returnnext;
    }//Spin自旋等待直到返為止置
}

整個JUC都是建立在CAS之上的,對於synchronized阻塞演算法,J.U.C在效能上有了很大的提升。會出現所謂的“ABA”問題

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 : 一般來說,獲取鎖和釋放鎖是成對兒的操作,這樣可以避免死鎖和資源的浪費。

注:在 finally 裡面做釋放鎖的操作

AQS
鎖機制實現的核心所在。AbstractQueuedSynchronizer是Lock/Executor實現的前提。
AQS實現:
基本的思想是表現為一個同步器,AQS支援下面兩個操作:

acquire:

while(synchronization state does not allow acquire){
    enqueue current threadifnot already queued;
    possibly block current thread;
}
dequeue current threadifit was queued;

release:

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)