咱們來看下ReentrantLock的基本用法
ThreadDomain35類java
public class ThreadDomain35 { private Lock lock = new ReentrantLock(); public void testMethod() { try { lock.lock(); for (int i = 0; i < 2; i++) { System.out.println("ThreadName = " + Thread.currentThread().getName() + ", i = " + i); } } finally { lock.unlock(); } } }
線程和main方法異步
public class MyThread35 extends Thread { private ThreadDomain35 td; public MyThread35(ThreadDomain35 td) { this.td = td; } public void run() { td.testMethod(); } public static void main(String[] args) { ThreadDomain35 td = new ThreadDomain35(); MyThread35 mt0 = new MyThread35(td); MyThread35 mt1 = new MyThread35(td); MyThread35 mt2 = new MyThread35(td); mt0.start(); mt1.start(); mt2.start(); } }
輸出結果ide
ThreadName = Thread-2, i = 0 ThreadName = Thread-2, i = 1 ThreadName = Thread-0, i = 0 ThreadName = Thread-0, i = 1 ThreadName = Thread-1, i = 0 ThreadName = Thread-1, i = 1
一個線程必須執行完才能執行下一個線程,說明ReentrantLock能夠加鎖。函數
ThreadDomain37類,methodB用synchronized修飾this
public class ThreadDomain37 { private Lock lock = new ReentrantLock(); public void methodA() { try { lock.lock(); System.out.println("MethodA begin ThreadName = " + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("MethodA end ThreadName = " + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public synchronized void methodB() { System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName()); System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName()); } }
MyThread37_0類線程
public class MyThread37_0 extends Thread { private ThreadDomain37 td; public MyThread37_0(ThreadDomain37 td) { this.td = td; } public void run() { td.methodA(); } }
MyThread37_1類debug
public class MyThread37_1 extends Thread { private ThreadDomain37 td; public MyThread37_1(ThreadDomain37 td) { this.td = td; } public void run() { td.methodB(); } }
MyThread37_main方法code
public class MyThread37_main { public static void main(String[] args) { ThreadDomain37 td = new ThreadDomain37(); MyThread37_0 mt0 = new MyThread37_0(td); MyThread37_1 mt1 = new MyThread37_1(td); mt0.start(); mt1.start(); } }
運行結果以下對象
MethodA begin ThreadName = Thread-0 MethodB begin ThreadName = Thread-1 MethodB begin ThreadName = Thread-1 MethodA end ThreadName = Thread-0
加了synchronized依然是異步執行,說明ReentrantLock和synchronized持有的對象監視器不一樣。ReentrantLock須要手動加鎖和釋放鎖。rem
synchronized與wait()和nitofy()/notifyAll()方法能夠實現等待/喚醒模型,ReentrantLock一樣能夠,須要藉助Condition的await()和signal/signalAll(),await()釋放鎖。
ThreadDomain38類
public class ThreadDomain38 { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void await() { try { lock.lock(); System.out.println("await時間爲:" + System.currentTimeMillis()); condition.await(); System.out.println("await等待結束"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signal() { try { lock.lock(); System.out.println("signal時間爲:" + System.currentTimeMillis()); condition.signal(); System.out.println("signal等待結束"); } finally { lock.unlock(); } } }
MyThread38類,線程和main方法
public class MyThread38 extends Thread { private ThreadDomain38 td; public MyThread38(ThreadDomain38 td) { this.td = td; } public void run() { td.await(); } public static void main(String[] args) throws Exception { ThreadDomain38 td = new ThreadDomain38(); MyThread38 mt = new MyThread38(td); mt.start(); Thread.sleep(3000); td.signal(); } }
運行結果以下
await時間爲:1563505465346 signal時間爲:1563505468345 signal等待結束 await等待結束
能夠看到,ReentrantLock和Condition實現了等待/通知模型。
一個Lock能夠建立多個Condition;
notify()喚醒的線程是隨機的,signal()能夠有選擇性地喚醒。
如今看一個利用Condition選擇等待和喚醒的例子
ThreadDomain47類,定義add和sub方法
public class ThreadDomain47 { private final Lock lock = new ReentrantLock(); private final Condition addCondition = lock.newCondition(); private final Condition subCondition = lock.newCondition(); private static int num = 0; private List<String> lists = new LinkedList<String>(); public void add() { lock.lock(); try { while(lists.size() == 10) {//當集合已滿,則"添加"線程等待 addCondition.await(); } num++; lists.add("add Banana" + num); System.out.println("The Lists Size is " + lists.size()); System.out.println("The Current Thread is " + "增長線程"); System.out.println("=============================="); this.subCondition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally {//釋放鎖 lock.unlock(); } } public void sub() { lock.lock(); try { while(lists.size() == 0) {//當集合爲空時,"減小"線程等待 subCondition.await(); } String str = lists.get(0); lists.remove(0); System.out.println("The Token Banana is [" + str + "]"); System.out.println("The Current Thread is " + "減小線程"); System.out.println("=============================="); num--; addCondition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
MyThread40_0類,增長線程
public class MyThread40_0 implements Runnable { private ThreadDomain47 task; public MyThread40_0(ThreadDomain47 task) { this.task = task; } @Override public void run() { task.add(); } }
MyThread40_1類,減小線程
public class MyThread40_1 implements Runnable { private ThreadDomain47 task; public MyThread40_1(ThreadDomain47 task) { this.task = task; } @Override public void run() { task.sub(); } }
main方法,啓動線程
public class MyThread40_main { public static void main(String[] args) { ThreadDomain47 task = new ThreadDomain47(); Thread t1=new Thread(new MyThread40_0(task)); Thread t3=new Thread(new MyThread40_0(task)); Thread t7=new Thread(new MyThread40_0(task)); Thread t8=new Thread(new MyThread40_0(task)); Thread t2 = new Thread(new MyThread40_1(task)); Thread t4 = new Thread(new MyThread40_1(task)); Thread t5 = new Thread(new MyThread40_1(task)); Thread t6 = new Thread(new MyThread40_1(task)); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); t7.start(); t8.start(); } }
輸出結果以下
The Lists Size is 1 The Current Thread is 增長線程 ============================== The Lists Size is 2 The Current Thread is 增長線程 ============================== The Token Banana is [add Banana1] The Current Thread is 減小線程 ============================== The Token Banana is [add Banana2] The Current Thread is 減小線程 ============================== The Lists Size is 1 The Current Thread is 增長線程 ============================== The Token Banana is [add Banana1] The Current Thread is 減小線程 ============================== The Lists Size is 1 The Current Thread is 增長線程 ============================== The Token Banana is [add Banana1] The Current Thread is 減小線程 ==============================
能夠看到,lists的數量不會增長太多,也不會減小太多。當集合滿,使增長線程等待,喚醒減小線程;當集合空,使減小線程等待,喚醒增長線程。咱們用wait()/notify()機制沒法實現該效果,這裏體現了Condition的強大之處。
公平鎖和非公平鎖
ReentrantLock能夠指定公平鎖和非公平鎖,公平鎖根據線程運行的順序獲取鎖,非公平鎖則經過搶佔得到鎖,不按線程運行順序。synchronized是非公平鎖。在ReentrantLock(boolean fair)構造函數傳入true/false來指定公平鎖/非公平鎖。
看個例子
ThreadDomain39類和main方法
public class ThreadDomain39 { private Lock lock = new ReentrantLock(true); public void testMethod() { try { lock.lock(); System.out.println("ThreadName" + Thread.currentThread().getName() + "得到鎖"); } finally { lock.unlock(); } } public static void main(String[] args) throws Exception { final ThreadDomain39 td = new ThreadDomain39(); Runnable runnable = new Runnable() { public void run() { System.out.println("線程" + Thread.currentThread().getName() + "運行了"); td.testMethod(); } }; Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) threads[i] = new Thread(runnable); for (int i = 0; i < 5; i++) threads[i].start(); } }
輸出結果以下
線程Thread-0運行了 ThreadNameThread-0得到鎖 線程Thread-1運行了 線程Thread-2運行了 ThreadNameThread-1得到鎖 線程Thread-3運行了 線程Thread-4運行了 ThreadNameThread-2得到鎖 ThreadNameThread-3得到鎖 ThreadNameThread-4得到鎖
能夠看到公平鎖得到鎖的順序和線程運行的順序相同。公平鎖儘量地讓線程獲取鎖的順序和線程運行順序保持一致,再執行幾回,可能不一致。
ReentrantLock構造函數傳入false,輸出結果以下:
線程Thread-0運行了 線程Thread-2運行了 線程Thread-4運行了 線程Thread-3運行了 ThreadNameThread-0得到鎖 線程Thread-1運行了 ThreadNameThread-1得到鎖 ThreadNameThread-2得到鎖 ThreadNameThread-4得到鎖 ThreadNameThread-3得到鎖
非公平鎖得到鎖的順序和線程運行的順序不一樣
獲取當前線程調用lock()的次數,通常debug使用。
看個例子
public class ThreadDomain40 { private ReentrantLock lock = new ReentrantLock(); public void testMethod1() { try { lock.lock(); System.out.println("testMethod1 getHoldCount = " + lock.getHoldCount()); testMethod2(); } finally { lock.unlock(); } } public void testMethod2() { try { lock.lock(); System.out.println("testMethod2 getHoldCount = " + lock.getHoldCount()); } finally { lock.unlock(); } } public static void main(String[] args) { ThreadDomain40 td = new ThreadDomain40(); td.testMethod1(); } }
輸出結果以下
testMethod1 getHoldCount = 1 testMethod2 getHoldCount = 2
能夠看到,testMethod1()被調用了一次,testMethod2()被調用了兩次,ReentrantLock和synchronized同樣,鎖都是可重入的。
getQueueLength()獲取等待的線程數量,isFair()判斷是不是公平鎖。
ThreadDomain41類和main方法,Thread.sleep(2000)使第一個線程以後的線程都來不及啓動,Thread.sleep(Integer.MAX_VALUE)使線程沒法unlock()。
public class ThreadDomain41 { public ReentrantLock lock = new ReentrantLock(); public void testMethod() { try { lock.lock(); System.out.println("ThreadName = " + Thread.currentThread().getName() + "進入方法!"); System.out.println("是否公平鎖?" + lock.isFair()); Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { final ThreadDomain41 td = new ThreadDomain41(); Runnable runnable = new Runnable() { public void run() { td.testMethod(); } }; Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) threads[i] = new Thread(runnable); for (int i = 0; i < 10; i++) threads[i].start(); Thread.sleep(2000); System.out.println("有" + td.lock.getQueueLength() + "個線程正在等待!"); } }
輸出結果以下
ThreadName = Thread-1進入方法! 是否公平鎖?false 有9個線程正在等待!
ReentrantLock默認是非公平鎖,只有一個線程lock(),9個線程在等待。
hasQueuedThread(Thread thread)查詢指定線程是否在等待鎖,hasQueuedThreads()查詢是否有線程在等待鎖。
看個例子
ThreadDomain41類和main方法,和上面例子相似,Thread.sleep(Integer.MAX_VALUE); 讓線程不釋放鎖,Thread.sleep(2000);讓第一個線程以後的線程都沒法啓動。
public class ThreadDomain42 extends ReentrantLock { public void waitMethod() { try { lock(); Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } finally { unlock(); } } public static void main(String[] args) throws InterruptedException { final ThreadDomain42 td = new ThreadDomain42(); Runnable runnable = new Runnable() { public void run() { td.waitMethod(); } }; Thread t0 = new Thread(runnable); t0.start(); Thread.sleep(500); Thread t1 = new Thread(runnable); t1.start(); Thread.sleep(500); Thread t2 = new Thread(runnable); t2.start(); Thread.sleep(500); System.out.println("t0 is waiting?" + td.hasQueuedThread(t0)); System.out.println("t1 is waiting?" + td.hasQueuedThread(t1)); System.out.println("t2 is waiting?" + td.hasQueuedThread(t2)); System.out.println("Is any thread waiting?" + td.hasQueuedThreads()); } }
輸出結果以下
t0 is waiting?false t1 is waiting?true t2 is waiting?true Is any thread waiting?true
t0線程得到了鎖,t0沒有釋放鎖,致使t1,t2等待鎖。
isHeldByCurrentThread()判斷鎖是否由當前線程持有,isLocked()判斷鎖是否由任意線程持有。
請看示例
ThreadDomain43類和main方法
public class ThreadDomain43 extends ReentrantLock { public void testMethod() { try { lock(); System.out.println(Thread.currentThread().getName() + "線程持有了鎖!"); System.out.println(Thread.currentThread().getName() + "線程是否持有鎖?" + isHeldByCurrentThread()); System.out.println("是否任意線程持有了鎖?" + isLocked()); } finally { unlock(); } } public void testHoldLock() { System.out.println(Thread.currentThread().getName() + "線程是否持有鎖?" + isHeldByCurrentThread()); System.out.println("是否任意線程持有了鎖?" + isLocked()); } public static void main(String[] args) { final ThreadDomain43 td = new ThreadDomain43(); Runnable runnable0 = new Runnable() { public void run() { td.testMethod(); } }; Runnable runnable1 = new Runnable() { public void run() { td.testHoldLock(); } }; Thread t0 = new Thread(runnable0); Thread t1 = new Thread(runnable1); t0.start(); t1.start(); } }
輸出結果以下
Thread-0線程持有了鎖! Thread-1線程是否持有鎖?false Thread-0線程是否持有鎖?true 是否任意線程持有了鎖?true 是否任意線程持有了鎖?true
Thread-0線程testMethod方法持有鎖,Thread-1線程testHoldLock方法沒有lock操做,因此不持有鎖。
tryLock()有加鎖的功能,得到了鎖且鎖沒有被另一個線程持有,此時返回true,不然返回false,能夠有效避免死鎖。tryLock(long timeout, TimeUnit unit)表示在給定的時間內得到了鎖,鎖沒有被其餘線程持有,且不處於中斷狀態。返回true,不然返回false;
看個例子
public class MyThread39 { public static void main(String[] args) { System.out.println("開始"); final Lock lock = new ReentrantLock(); new Thread() { @Override public void run() { String tName = Thread.currentThread().getName(); if (lock.tryLock()) { System.out.println(tName + "獲取到鎖!"); } else { System.out.println(tName + "獲取不到鎖!"); return; } try { for (int i = 0; i < 5; i++) { System.out.println(tName + ":" + i); } Thread.sleep(5000); } catch (Exception e) { System.out.println(tName + "出錯了!"); } finally { System.out.println(tName + "釋放鎖!"); lock.unlock(); } } }.start(); new Thread() { @Override public void run() { String tName = Thread.currentThread().getName(); try { if (lock.tryLock(1,TimeUnit.SECONDS)) { System.out.println(tName + "獲取到鎖!"); } else { System.out.println(tName + "獲取不到鎖!"); return; } } catch (InterruptedException e) { e.printStackTrace(); } try { for (int i = 0; i < 5; i++) { System.out.println(tName + ":" + i); } } catch (Exception e) { System.out.println(tName + "出錯"); } finally { System.out.println(tName + "釋放鎖!"); lock.unlock(); } } }.start(); System.out.println("結束"); } }
輸出結果以下
開始 Thread-0獲取到鎖! Thread-0:0 Thread-0:1 Thread-0:2 Thread-0:3 Thread-0:4 結束 Thread-1獲取不到鎖! Thread-0釋放鎖!
Thread-0先得到了鎖,且sleep了5秒,致使Thread-1獲取不到鎖,咱們給Thread-1的tryLock設置1秒,一秒內獲取不到鎖就會返回false。
若是Thread.sleep(0),那麼Thread-0和Thread-1均可以得到鎖,園友能夠本身試下。
1.synchronized關鍵字是語法層面的實現,ReentrantLock要手動lock()和unlock(); 2.synchronized是不公平鎖,ReentrantLock能夠指定是公平鎖仍是非公平鎖; 3.synchronized等待/喚醒機制是隨機的,ReentrantLock藉助Condition的等待/喚醒機制能夠自行選擇等待/喚醒;