java高併發實戰程式設計(一)
總覽:什麼是鎖:鎖就是一種資源,用來進行共享資源的保護操作(也可以認為鎖是一條封鎖線,一個物件獲得了可以通過,否則就只能在隔離帶後面等待,除非輪到自己獲取到許可權)。
一.synchronized功能拓展:重入鎖 ReentrantLock
1.效能和synchronized 相差不大程式碼,程式碼:
package com.fortune.util; import java.util.concurrent.locks.ReentrantLock; //重入鎖代替synchronized 兩者消耗差不多兩千萬次迴圈 public class ThreadUtils implements Runnable { public static ReentrantLock lock = new ReentrantLock(); public static int i=0; @Override public void run() { for (int j=0;j<10000000;j++){ lock.lock(); try { i++; }finally { lock.unlock(); } /*synchronized (this){ i++; }*/ } } public static void main(String[] args) throws InterruptedException { Long st = System.currentTimeMillis(); ThreadUtils tt = new ThreadUtils(); Thread t1 = new Thread(tt); Thread t2 = new Thread(tt); t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); System.out.println("消耗時間:"+(System.currentTimeMillis()-st)); }
總結:ReentrantLock需要手動加鎖以及釋放鎖(顯示操作),因此靈活度高,但是要在退出臨界區要釋放鎖,否則其它執行緒也進入不了。之所以叫 重入鎖,是因為可以反覆進入,但是僅限於一個執行緒,如上執行緒可以改為:
lock.lock();
lock.lock();
try {
i++;
}finally {
lock.unlock();
lock.unlock();
}
注意:鎖定了幾次就要釋放幾次,獲取以及釋放鎖的消耗時間相比於第一次會大很多。
2.ReentrantLock 可以中斷響應:對於synchronized,如果一個執行緒在等待鎖,要麼獲得鎖繼續執行,要麼保持等待,不能中途退出。而使用重入鎖則可以被中斷
3.限時鎖:在一定時間內嘗試獲取鎖失敗之後,即返回(不進行任何操作),方法Reentrant().tryLock(),如果不帶引數,當有執行緒獲取到鎖後就直接返回 true,否則立即返回false,不會進行等待,不會產生死鎖。程式碼:
public class ThreadUtils implements Runnable { public static ReentrantLock lock = new ReentrantLock(); public void run() { try { //在5s內嘗試獲取鎖 if (lock.tryLock(5, TimeUnit.SECONDS)){ //這個thread應該是當前執行的執行緒 System.out.println("我獲取到鎖了"); Thread.sleep(6000); lock.unlock(); }else { System.out.println("get lock failed"); } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { ThreadUtils threadUtils = new ThreadUtils(); Thread t1 = new Thread(threadUtils); Thread t2 = new Thread(threadUtils); t1.start();t2.start(); } }
4.公平鎖:大多數情況下瑣是不公平的。當執行緒a請求了鎖1,執行緒b也請求了鎖1,當鎖1可用時,到底是先分配a還是b。系統只會從等待佇列中隨機選取一個,不能保證其公平性。而公平鎖就是為了保證先到先得,按照申請的時間順序進行鎖的分配。不會產生飢餓。總會獲取到資源。而synchronized則是非公平鎖,利用Public Reentrant(boolean fair);進行設定。
根據系統排程:一個執行緒會傾向於再次獲取已經持有的鎖,也就是說一個執行緒第一次獲取了鎖之後,下一次獲取鎖的概率就相對來說大一點。這種方式很高效,但是是不公平的,而公平鎖需要一個有序佇列進行記錄,非常消耗系統資源。
5.重入鎖的好搭檔:Condition條件
condition物件通過呼叫方法實現和wait()以及notify()方法作用相同。但是 wait()和notify()都是和synchronized 關鍵字合作使用。 而Condition是與重入鎖相關聯的。通過Lock介面的Condition.newCondition()方法生成一個與當前重入鎖繫結的condition例項。利用Condition物件,可以讓執行緒進行等待或者得到通知等。
方法:
await() 方法會使當前執行緒等待,同時釋放鎖,當其他執行緒中使用signal()或者signalAll()方法時,執行緒會重新獲得鎖,並繼續執行,或者執行緒被中斷,也能跳出等待和Object.wait()方法相似
awaitUninterruptibly()方法與 await()方法基本相同,但是不會在等待過程中響應中斷。
signal()方法用於喚醒一個在等待中的執行緒。相對signalAll()會喚醒所有在等待的執行緒。和Object.notify()方法類似。
程式碼:
public class ThreadUtils implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
public void run() {
lock.lock();
try {
condition.await();
System.out.println("Thread is going on");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ThreadUtils threadUtils = new ThreadUtils();
Thread t1 = new Thread(threadUtils);
t1.start();
Thread.sleep(7000);
//通知執行緒t1繼續執行(加鎖是為了防止共享資源衝突,因為執行緒喚醒),因為最開始的鎖已經釋放過了,此時共享資源未獲取鎖。
lock.lock();
condition.signal();
//喚醒一個執行緒,此時需要將共享資源的鎖進行釋放,否則喚醒了也獲取不到鎖,一直保持等待狀態。
lock.unlock();
}
}
和Object().wait()和notify()方法一樣,當執行緒使用Condition.await()時,要求執行緒持有相關的重入鎖,(沒獲取鎖進行此操作會報錯)在Condition.await()呼叫後,這個執行緒會釋放這把鎖。同理在Condition.signal()方法呼叫時,也要求執行緒先獲得相關的鎖。在signal()方法呼叫後,系統會從當前Condition物件的等待佇列中,喚醒一個執行緒。一旦執行緒被喚醒,會重新嘗試獲得與之繫結的重入鎖,成功獲取繼續執行,因此在signal()方法呼叫之後,一般需要釋放相關的鎖。謙讓給被喚醒的執行緒,否則喚醒了執行緒但也無法獲得鎖,因此無法真正繼續執行。