您好,登錄后才能下訂單哦!
加鎖和解鎖
我們來看下ReentrantLock的基本用法
ThreadDomain35類
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(); } }
輸出結果
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可以加鎖。
ReentrantLock持有的對象監視器和synchronized不同
ThreadDomain37類,methodB用synchronized修飾
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類
public class MyThread37_1 extends Thread { private ThreadDomain37 td; public MyThread37_1(ThreadDomain37 td) { this.td = td; } public void run() { td.methodB(); } }
MyThread37_main方法
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需要手動加鎖和釋放鎖。
Condition
基本用法
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選擇 喚醒/等待
現在看一個利用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中的方法
公平鎖和非公平鎖
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獲得鎖
非公平鎖獲得鎖的順序和線程運行的順序不同
getHoldCount()
獲取當前線程調用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()
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()和hasQueuedThreads()
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()
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()和tryLock(long timeout, TimeUnit unit)
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都可以獲得鎖,園友可以自己試下。
synchronized和ReentrantLock的比較
1.synchronized關鍵字是語法層面的實現,ReentrantLock要手動lock()和unlock();
2.synchronized是不公平鎖,ReentrantLock可以指定是公平鎖還是非公平鎖;
3.synchronized等待/喚醒機制是隨機的,ReentrantLock借助Condition的等待/喚醒機制可以自行選擇等待/喚醒;
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。