執行緒基礎(二十一)-併發容器-ArrayBlockingQueue(上)
本文作者:王一飛,叩丁狼高階講師。原創文章,轉載請註明出處。
在正式講解ArrayBlockingQueue類前,先來科普一下執行緒中各類鎖,只有瞭解這些鎖之後,理解ArrayBlockingQueue那就更輕鬆了。
可重入鎖
一種遞迴無阻塞的同步機制,也叫做遞迴鎖。簡單講一個執行緒獲取到鎖物件之後,還是可以再次獲取該鎖物件時,不會發生阻塞。
java中 synchronized 跟ReentrantLock 都是可重入鎖, synchronized 為隱性, 而ReentrantLock 為顯示。 下面以synchronized 為例:
public class ThreadDemo { public synchronized void method1(){ System.out.println(Thread.currentThread().getName() + "進入method1...."); try { Thread.sleep(1000); method2(); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void method2(){ System.out.println(Thread.currentThread().getName() + "進入method2...."); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class App { public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo(); new Thread(new Runnable() { @Override public void run() { threadDemo.method1(); } }, "t1").start(); new Thread(new Runnable() { @Override public void run() { threadDemo.method2(); } }, "t2").start(); new Thread(new Runnable() { @Override public void run() { threadDemo.method2(); } }, "t3").start(); } }
執行結果
上面程式碼, t1執行緒先進入method1, 1s之後進入method2, 因為使用synchronized 加鎖,t2, t3執行緒無法進入method2,必須等,而t1執行緒執行完method1後,可以直接進入method2, 無需要重複獲取鎖的操作。
改進: 可以再多開幾個執行緒訪問method2方法,會發現結果是一致的。
不可重入鎖
不可重入鎖是跟重入鎖是對立的,表示一執行緒獲取到鎖物件後,想再次獲取該鎖物件時,必須先釋放之前獲取鎖物件,否則阻塞等待。
java中沒有執行緒類實現不可重入鎖,更多時候,需要我們程式設計實現。
//自定義鎖物件模擬不可重入鎖 public class MyLock { private boolean isLock = false; //模擬獲取鎖 public synchronized void lock() throws InterruptedException { //自旋排除一些硬體執行干擾 while(isLock){ wait(); } isLock = true; } //模擬釋放鎖 public synchronized void unLock(){ isLock = false; notify(); } }
public class ThreadDemo {
private MyLock lock = new MyLock();
public void method1() throws InterruptedException {
lock.lock();
System.out.println("method1...in");
method2();
System.out.println("method1...out");
lock.unLock();
}
public void method2() throws InterruptedException {
lock.lock();
System.out.println("method2......");
lock.unLock();
}
}
public class App {
public static void main(String[] args) throws InterruptedException {
ThreadDemo demo = new ThreadDemo();
demo.method1();
}
}
執行之後, 列印只有method1...in, 在執行method1時,呼叫lock.lock()方法, isLock標記量改為true,表示鎖被持有,跳過迴圈。 執行method2時,再次執行lock.lock(),isLock標籤為true, 進入迴圈,執行緒等待。模擬拉當鎖已經被持有,同一個執行緒第二次申請同一把鎖,需要等待。
互斥鎖
同一個時刻,只允許獲取到所物件的執行緒執行。synchronized ReentrantLock 本身就是互斥鎖。
public class ThreadDemo {
//同一個時刻只允許一個執行緒進入
public synchronized void method1(){
System.out.println(Thread.currentThread().getName() + "進入....");
}
}
public class ThreadDemo {
private ReentrantLock lock = new ReentrantLock();
//同一個時刻只允許一個執行緒進入
public void method1(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "進入....");
}finally {
lock.unlock();
}
}
}
自旋鎖
一種非阻塞鎖,也就是說,如果某執行緒需要獲取鎖,但鎖已經被執行緒佔用時,執行緒不阻塞等待,而是通過空迴圈來消耗CPU時間片,等待其他執行緒釋放鎖。注意,自旋鎖中的迴圈也不是瞎迴圈, 一般會設定一定迴圈次數或者迴圈跳出條件。
自旋鎖運用非常廣泛, jdk中的juc包原子操作類中都是, 比如: AtomicInteger
public class AtomicInteger extends Number implements java.io.Serializable {
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
}
Unsafe類
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
偏向鎖 / 輕量級鎖 / 重量級鎖
偏向鎖,輕量鎖,重量鎖並不是對執行緒加鎖機制,而是jdk1.6之後提出的對執行緒加鎖的優化策略。
偏向鎖:當執行緒沒有競爭的環境下,需要重複獲取某個鎖物件時,jvm為減少開銷,讓執行緒進入偏向模式,再次獲取鎖物件時,取消之前已經獲取鎖同步操作(即一系列的cas判斷),直接取得鎖物件。如果期間有其他執行緒參與競爭,則退出偏向模式。
當執行緒退出偏向模式最後,進入輕量級鎖模式。此時,執行緒嘗試使用自旋方式來獲取鎖,如果獲取成功,繼續邏輯執行, 如果獲取失敗,表示當前鎖物件存在競爭,鎖就會膨脹成重量級鎖。
當執行緒進入重量級鎖模式, 所有操作就跟我們所認知那樣,爭奪CUP,在操作過程中,爭奪失敗執行緒會被作業系統掛起,阻塞等待。那麼執行緒間的切換和呼叫成本就會大大提高,效能也就對應下降了。
樂觀鎖
顧名思義,就是很樂觀,在獲取資料時認為別人不會對資料做修改,所以不上鎖,但是在更新的時候會先判斷別人有沒有更新了此資料,最通用的實現是使用版本號判斷方式,java的juc併發包中的原子操作類使用的CAS機制,其實也是一種樂觀鎖實現。
悲觀鎖
與樂觀鎖是相對的,操作前都假設最壞的情況,在獲取資料的認為別人會對資料做修改,所以每次操作前都會上鎖。而別人想操作此資料就會阻塞直到它拿到鎖。Java中synchronized關鍵字ReentrantLock的實現便是悲觀鎖。
公平鎖
公平鎖,講究公平,當鎖物件被佔用時,參與鎖物件爭奪的執行緒按照FIFO的順序排序等待鎖釋放,人人有機會,不爭不搶。
public class Resource implements Runnable {
private ReentrantLock lock = new ReentrantLock(true); //公平鎖
public void run() {
System.out.println(Thread.currentThread().getName() + " 進入了.....");
lock.lock(); //爭鎖
try {
System.out.println(Thread.currentThread().getName() + " 獲取鎖並執行了.....");
}finally {
lock.unlock();
}
}
}
public class App {
public static void main(String[] args) throws InterruptedException {
Resource resource = new Resource(); //共享資源
for (int i = 0; i <10; i++) {
new Thread(resource, "t_" + i).start();
}
}
}
t_4 進入了.....
t_2 進入了.....
t_4 獲取鎖並執行了.....
t_7 進入了.....
t_6 進入了.....
t_3 進入了.....
t_5 進入了.....
t_2 獲取鎖並執行了.....
t_7 獲取鎖並執行了.....
t_6 獲取鎖並執行了.....
t_3 獲取鎖並執行了.....
t_5 獲取鎖並執行了.....
t_0 進入了.....
t_0 獲取鎖並執行了.....
t_1 進入了.....
t_1 獲取鎖並執行了.....
t_8 進入了.....
t_8 獲取鎖並執行了.....
t_9 進入了.....
t_9 獲取鎖並執行了.....
觀察執行結果,當鎖是公平鎖時(new ReentrantLock(true))會發現進入順序是4,2,7,6,3,5,0,1,8,9 而執行的順序是4,2,7,6,3,5,0,1,8,9。兩者順序一樣,這就是公平的體現,誰先來,誰先執行。
非公平鎖
與公平鎖相對,當鎖物件被釋放時,所有參與爭奪鎖物件的執行緒各憑本事,撐死膽大的,餓死膽小的。
其他程式碼不變,僅僅將引數改為false或者去掉
private ReentrantLock lock = new ReentrantLock(false); //非公平鎖
t_4 進入了.....
t_2 進入了.....
t_5 進入了.....
t_3 進入了.....
t_4 獲取鎖並執行了.....
t_7 進入了.....
t_7 獲取鎖並執行了.....
t_0 進入了.....
t_1 進入了.....
t_0 獲取鎖並執行了.....
t_8 進入了.....
t_2 獲取鎖並執行了.....
t_9 進入了.....
t_9 獲取鎖並執行了.....
t_6 進入了.....
t_5 獲取鎖並執行了.....
t_3 獲取鎖並執行了.....
t_1 獲取鎖並執行了.....
t_8 獲取鎖並執行了.....
t_6 獲取鎖並執行了.....
當鎖是非公平鎖時(new ReentrantLock(false))進入的順序與執行順序不一樣啦,這就是非公平鎖,爭奪CPU各憑本事。
好的,到這執行緒中的鎖知識普及就結束了。
想獲取更多技術乾貨,請前往叩丁狼官網:http://www.wolfcode.cn/all_article.html