Android多執行緒-----併發和同步(Lock)
一、為什麼需要Lock
如果一個程式碼塊被synchronized修飾了,當一個執行緒獲取了對應的鎖,並執行該程式碼塊時,其他執行緒便只能一直等待,等待獲取鎖的執行緒釋放鎖,而這裡獲取鎖的執行緒釋放鎖只會有兩種情況:
1)獲取鎖的執行緒執行完了該程式碼塊,然後執行緒釋放對鎖的佔有;
2)執行緒執行發生異常,此時JVM會讓執行緒自動釋放鎖。
那麼如果這個獲取鎖的執行緒由於要等待IO或者其他原因(比如呼叫sleep方法)被阻塞了,但是又沒有釋放鎖,其他執行緒便只能等待,試想一下,這多麼影響程式執行效率。
因此就需要有一種機制可以不讓等待的執行緒一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。通過Lock可以知道執行緒有沒有成功獲取到鎖。這個是synchronized無法辦到的。
二、synchronized的兩大不足
第一大不足:由於我們沒辦法設定synchronized關鍵字在獲取鎖的時候等待時間,所以synchronized可能會導致執行緒為了加鎖而無限期地處於阻塞狀態。
第二大不足:使用synchronized關鍵字等同於使用了互斥鎖,即其他執行緒都無法獲得鎖物件的訪問權。這種策略對於讀多寫少的應用而言是很不利的,因為即使多個讀者看似可以併發執行,但他們實際上還是序列的,並將最終導致併發效能的下降。
雖然synchronized已經作為一個關鍵字被固化在Java語言中了,但它只提供了一種相當保守的執行緒安全策略,且該策略開放給程式設計師的控制能力極弱。
三、synchronized與Lock的區別
類別 | synchronized | Lock |
存在層次 | 關鍵字,是內建的語言實現 | 是一個類 |
發生異常 | 會自動釋放執行緒佔有的鎖,因此不會導致死鎖現象發生 | 如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖 |
鎖的釋放 | 1、以獲取鎖的執行緒執行完同步程式碼,釋放鎖 2、執行緒執行發生異常,jvm會讓執行緒釋放鎖 3、不需要主動釋放鎖 |
1、在finally中必須釋放鎖,不然容易造成執行緒死鎖 2、需要主動釋放鎖 |
鎖的獲取 | 假設A執行緒獲得鎖,B執行緒等待。 如果A執行緒阻塞,B執行緒會一直等待 |
分情況而定,Lock有多個鎖獲取的方式,就是可以嘗試獲得鎖,執行緒可以不用一直等待 |
鎖狀態 | 無法判斷 | 可以判斷 |
響應中斷 | 不行 | 行 |
鎖型別 | 可重入 不可中斷 非公平 | 可重入 可判斷 可公平(兩者皆可) |
效能 | 少量同步 | 大量同步 可以提高多個執行緒進行讀操作的效率 |
可重入鎖 | 是 | 是 |
如何選擇 | 如果競爭資源不激烈,兩者的效能是差不多的,而當競爭資源非常激烈時(即有大量執行緒同時競爭),此時Lock的效能要遠遠優於synchronized |
四、java.util.concurrent.locks包下常用的類
1、Lock是一個介面
public interface Lock {
/**
* 就是用來獲取鎖。如果鎖已被其他執行緒獲取,則進行等待;該方法是平常使用得最多的一個方法
*/
void lock();
/**
* 1、用該鎖的獲得方式,如果執行緒在獲取鎖的階段進入了等待,那麼可以中斷此執行緒,即中斷執行緒的等待狀態,先去做別的事
* 2、也就使說,當兩個執行緒同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時執行緒A獲取到了鎖,而執行緒B只有在等待,
* 那麼對執行緒B呼叫threadB.interrupt()方法能夠中斷執行緒B的等待過程。
* 3、由於lockInterruptibly()的宣告中丟擲了異常,所以lock.lockInterruptibly()必須放在try塊中或者在呼叫
* lockInterruptibly()的方法外宣告丟擲InterruptedException。
* 4、當一個執行緒獲取了鎖之後,是不會被interrupt()方法中斷的。
* 而用synchronized修飾的話,當一個執行緒處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。
*/
void lockInterruptibly() throws InterruptedException;
/**
* 嘗試鎖定
* 注意返回型別是boolean,如果獲取鎖的時候鎖被佔用就返回false,否則返回true
* 也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。
*/
boolean tryLock();
/**
* 嘗試鎖定
* 比起tryLock(),區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。
* 如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* 釋放鎖
* 使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生
*/
void unlock();
}
2、ReentrantLock
意思是“可重入鎖”,是唯一實現了Lock介面的類,並且ReentrantLock提供了更多的方法。
重入鎖的中斷響應功能就合理地避免了這樣的情況。比如,一個正在等待獲取鎖的執行緒被“告知”無須繼續等待下去,就可以停止工作了。
3、如何使用Lock
public class Test {
private ArrayList<Integer> arrayList = new ArrayList<Integer>();
/**
* 1、如果lock變數是區域性變數,每個執行緒執行該方法時都會儲存一個副本,那麼理所當然每個執行緒執行到lock.lock()處獲取的是不同的鎖,所以就不會發生衝突。
* 2、如果lock是全域性變數,則才會發生衝突
*/
private Lock lock = new ReentrantLock(); //注意這個地方
public static void main(String[] args) {
final Test test = new Test();
new Thread(){
public void run() {
test.insert(Thread.currentThread());
};
}.start();
new Thread(){
public void run() {
test.insert(Thread.currentThread());
};
}.start();
}
public void insert(Thread thread) {
if(lock.tryLock()) {
try {
System.out.println(thread.getName()+"得到了鎖");
for(int i=0;i<5;i++) {
arrayList.add(i);
}
} catch (Exception e) {
// TODO: handle exception
}finally {
System.out.println(thread.getName()+"釋放了鎖");
lock.unlock();
}
} else {
System.out.println(thread.getName()+"獲取鎖失敗");
}
}
}
輸出結果
Thread-0得到了鎖
Thread-1獲取鎖失敗
Thread-0釋放了鎖
2、公平鎖
3、ReadWriteLock、ReentrantReadWriteLock
是Lock的另一種實現方式,我們已經知道了ReentrantLock是一個排他鎖,同一時間只允許一個執行緒訪問,而ReentrantReadWriteLock允許多個讀執行緒同時訪問,但不允許寫執行緒和讀執行緒、寫執行緒和寫執行緒同時訪問。相對於排他鎖,提高了併發性。在實際應用中,大部分情況下對共享資料(如快取)的訪問都是讀操作遠多於寫操作,這時ReentrantReadWriteLock能夠提供比排他鎖更好的併發性和吞吐量。
感謝:
https://blog.csdn.net/u012403290/article/details/64910926?locationNum=11&fps=1
https://www.cnblogs.com/handsomeye/p/5999362.html