本文是筆者看了《實戰java高併發程序設計》以後加上本身的理解所寫的筆記。java
之因此直接從併發工具開始,是由於多線程的基礎知識,例如多線程建立,經常使用的方法,以及synchronized,volatile關鍵字等知識以前學習的時候已經學習過許多遍了,可是java併發包卻鮮有接觸,此次決定寫成博客,系列的總結一下。(以後有時間的話再把前面的知識補充上來,讓這個系列更完整一些)多線程
Reentrantlock(重入鎖) 併發
簡單看一下它的用法:less
重入鎖基本代碼演示:jvm
package thread.thread_util; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 展現重入鎖的用法 */ public class Lesson16_ReetrantLock { public static void main(String[] args) { Runnable target = new Runnable() { private Lock lock = new ReentrantLock(); @Override public void run() { lock.lock(); // lock.lock(); try { for (int i = 0; i < 5 ; i++) { System.out.println(Thread.currentThread().getId() + ": " + i); } } finally { lock.unlock(); // lock.unlock(); } } }; Thread t1 = new Thread(target); Thread t2 = new Thread(target); t1.start(); t2.start(); } }
重入究竟是什麼意思?ide
相比synchronized關鍵字,重入鎖顯示的調用了加鎖和解鎖的時機,可是爲何要叫重入鎖呢?重入二字是什麼意思呢?函數
重入的意思就是同一個線程能夠反覆進入同一個鎖,上面的代碼中就算把註釋給去掉代碼也是能夠執行的。高併發
固然這個代碼例子仍然不夠貼切,誰會沒事加兩把鎖上去呢?可是你能夠想象一下遞歸,咱們都知道遞歸的每次操做都把相關的變量壓入了一個棧之中,執行完成就彈出棧,而後再執行上一層函數,這裏若是咱們進行了加鎖操做的話,那麼每一層函數在遞推的時候就都會加上一層鎖,而在迴歸的時候函數結束,則會釋放鎖。重入鎖的內部有一個計數器,每加一個鎖,計數器就加1,釋放一個鎖,計數器就減1,只有當計數器爲0時,鎖才能被釋放。工具
咱們以經典的斐波那契數做爲例子來展現一下。學習
深刻理解「重入」代碼演示:
package thread.thread_util; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 遞歸中的重入鎖 */ public class Lesson16_ReentrantLock02 { public static void main(String[] args) { Runnable target = new Runnable() { private Lock lock = new ReentrantLock(); @Override public void run() { System.out.println(Thread.currentThread().getId()); System.out.println("最終結果爲: " + fibonacci(3)); } public int fibonacci (int n) { try { lock.lock(); System.out.println(Thread.currentThread().getId() + "得到了鎖"); if( n == 1 ) return 1; if( n == 2 ) return 1; return fibonacci(n-1) + fibonacci(n-2); } finally { lock.unlock(); System.out.println(Thread.currentThread().getId() + "釋放了鎖"); } } }; Thread thread = new Thread(target); thread.start(); } }
結果:
11
11得到了鎖
11得到了鎖
11釋放了鎖
11得到了鎖
11釋放了鎖
11釋放了鎖
最終結果爲: 2
咱們能夠看到這裏總共得到了3次鎖,釋放了3次鎖,整個鎖纔會被最終釋放。
你能夠再開一個線程進行驗證,結果會相似下面這樣:
11 11得到了鎖 11得到了鎖 11釋放了鎖 11得到了鎖 11釋放了鎖 11釋放了鎖 最終結果爲: 2 12 12得到了鎖 12得到了鎖 12釋放了鎖 12得到了鎖 12釋放了鎖 12釋放了鎖 最終結果爲: 2
重入鎖的好基友:Condition對象
Condition的功能和Object.wait()與Object.notify()比較類似,它讓咱們能夠控制某個線程何時開始等待,何時被喚醒,而不是由jvm來決定。
條件對象代碼演示:
package thread.thread_util; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 展現條件對象的基本用法 * await * signal * signalAll */ public class Lesson17_Condition01 implements Runnable{ public static Lock lock = new ReentrantLock(); public static Condition condition = lock.newCondition(); @Override public void run() { try { lock.lock(); condition.await(); System.out.println("Thread is going on"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws Exception{ Runnable target = new Lesson17_Condition01(); Thread thread = new Thread(target); thread.start(); Thread.sleep(2000); lock.lock(); condition.signal(); lock.unlock(); } }
這裏在Runnable對象中,將對象鎖住並讓線程進入了休眠狀態
主線程中進行了2秒鐘的休眠,這2秒鐘內子線程也沒法執行,由於被「await()」了,直到2秒鐘後,主線程再次開始執行,對線程進行了喚醒(signal)操做,喚醒操做也要進行加鎖操做。
關於重入鎖還有幾點須要說明:
而對於重入鎖來講,還存在另一種狀態,那就是中斷線程,也就是把等待隊列中的線程中斷,那麼這個中斷有什麼用呢?
好比說若是出現了死鎖的狀況,兩個線程都在等待對方釋放資源,那麼這個時候若是我主動的中斷一個線程,那麼這個線程所持有的資源就被釋放啦。咱們來看看代碼
重入鎖中斷響應代碼演示:
package thread.thread_util; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 展現重入鎖的中斷響應功能 * 首先構造一個死鎖 * 而後中斷某個線程,釋放資源 * 最後另一個線程就能夠得到釋放的資源,完成線程的任務 */ public class Lesson16_ReentrantLock03 implements Runnable{ private static ReentrantLock lock1 = new ReentrantLock(); private static ReentrantLock lock2 = new ReentrantLock(); private int flag = 0; public void setFlag(int val) { this.flag = val; } @Override public void run() { try { if(flag == 1) { lock1.lockInterruptibly(); Thread.sleep(1000); lock2.lockInterruptibly(); System.out.println("Thread1 is going on "); } else { lock2.lockInterruptibly(); Thread.sleep(1000); lock1.lockInterruptibly(); System.out.println("Thread2 is going on "); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if(lock1.isHeldByCurrentThread()){ lock1.unlock(); } if(lock2.isHeldByCurrentThread()){ lock2.unlock(); } } } public static void main(String[] args) { Lesson16_ReentrantLock03 target1 = new Lesson16_ReentrantLock03(); Lesson16_ReentrantLock03 target2 = new Lesson16_ReentrantLock03(); target1.setFlag(1); target2.setFlag(2); Thread thread1 = new Thread(target1); Thread thread2 = new Thread(target2); thread1.start(); thread2.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } thread2.interrupt(); } }
Acquires the lock unless the current thread is interrupted. (文檔說明)
最後線程2進行了中斷,代碼中的「else」裏面的代碼中的lockInterruptibly()方法就會使線程2 釋放lock2並放棄對lock1的請求。
運行結果:
Thread1 is going on java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) at thread.thread_util.Lesson16_ReentrantLock03.run(Lesson16_ReentrantLock03.java:33) at java.lang.Thread.run(Thread.java:748) Process finished with exit code 0
鎖申請等待時限(tryLock)
這是主動的中斷,還有另一種更爲簡便的方法,就是給鎖的申請添加時限,若是規定時間內我還沒獲得鎖,那我就不要這把鎖了
trylock() 代碼演示:
package thread.thread_util; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; public class Lesson16_ReentrantLock04 implements Runnable{ private static ReentrantLock lock = new ReentrantLock(); @Override public void run () { try { if(lock.tryLock(3,TimeUnit.SECONDS)){ System.out.println(Thread.currentThread().getName() + " get lock successful"); Thread.sleep(4000); } else { System.out.println(Thread.currentThread().getName() + " get lock failed"); } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Lesson16_ReentrantLock04 target = new Lesson16_ReentrantLock04(); Thread t1 = new Thread(target); Thread t2 = new Thread(target); t1.setName("線程1"); t2.setName("線程2"); t1.start(); t2.start(); } }
這裏線程1先得到了鎖,而後睡眠6秒鐘(sleep並不會釋放鎖),因此線程2在3秒鐘內都沒法得到所最後進入else代碼段。
最後結果:
線程1 get lock successful
線程2 get lock failed
關於重入鎖和條件對象暫時就寫到這裏。