與synchronized相似的,lock也可以達到同步的效果html
步驟 1 : 回憶 synchronized 同步的方式java
首先回憶一下 synchronized 同步對象的方式多線程
當一個線程佔用 synchronized 同步對象,其餘線程就不能佔用了,直到釋放這個同步對象爲止this
package multiplethread; import java.text.SimpleDateFormat; import java.util.Date; public class TestThread { public static String now(){ return new SimpleDateFormat("HH:mm:ss").format(new Date()); } public static void main(String[] args) { final Object someObject = new Object(); Thread t1 = new Thread(){ public void run(){ try { System.out.println( now()+" t1 線程已經運行"); System.out.println( now()+this.getName()+ " 試圖佔有對象:someObject"); synchronized (someObject) { System.out.println( now()+this.getName()+ " 佔有對象:someObject"); Thread.sleep(5000); System.out.println( now()+this.getName()+ " 釋放對象:someObject"); } System.out.println(now()+" t1 線程結束"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; t1.setName(" t1"); t1.start(); Thread t2 = new Thread(){ public void run(){ try { System.out.println( now()+" t2 線程已經運行"); System.out.println( now()+this.getName()+ " 試圖佔有對象:someObject"); synchronized (someObject) { System.out.println( now()+this.getName()+ " 佔有對象:someObject"); Thread.sleep(5000); System.out.println( now()+this.getName()+ " 釋放對象:someObject"); } System.out.println(now()+" t2 線程結束"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; t2.setName(" t2"); t2.start(); } }
步驟 2 : 使用Lock對象實現同步效果spa
Lock是一個接口,爲了使用一個Lock對象,須要用到線程
Lock lock = new ReentrantLock();
與 synchronized (someObject) 相似的,lock()方法,表示當前線程佔用lock對象,一旦佔用,其餘線程就不能佔用了。 與 synchronized 不一樣的是,一旦synchronized 塊結束,就會自動釋放對someObject的佔用。 lock卻必須調用unlock方法進行手動釋放,爲了保證釋放的執行,每每會把unlock() 放在finally中進行。設計
package multiplethread; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestThread { public static String now() { return new SimpleDateFormat("HH:mm:ss").format(new Date()); } public static void log(String msg) { System.out.printf("%s %s %s %n", now() , Thread.currentThread().getName() , msg); } public static void main(String[] args) { Lock lock = new ReentrantLock(); Thread t1 = new Thread() { public void run() { try { log("線程啓動"); log("試圖佔有對象:lock"); lock.lock(); log("佔有對象:lock"); log("進行5秒的業務操做"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } finally { log("釋放對象:lock"); lock.unlock(); } log("線程結束"); } }; t1.setName("t1"); t1.start(); try { //先讓t1飛2秒 Thread.sleep(2000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } Thread t2 = new Thread() { public void run() { try { log("線程啓動"); log("試圖佔有對象:lock"); lock.lock(); log("佔有對象:lock"); log("進行5秒的業務操做"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } finally { log("釋放對象:lock"); lock.unlock(); } log("線程結束"); } }; t2.setName("t2"); t2.start(); } }
步驟 3 : trylock方法code
synchronized 是不佔用到手不罷休的,會一直試圖佔用下去。 與 synchronized 的鑽牛角尖不同,Lock接口還提供了一個trylock方法。 trylock會在指定時間範圍內試圖佔用,佔成功了,就啪啪啪。 若是時間到了,還佔用不成功,扭頭就走~orm
注意: 由於使用trylock有可能成功,有可能失敗,因此後面unlock釋放鎖的時候,須要判斷是否佔用成功了,若是沒佔用成功也unlock,就會拋出異常htm
package multiplethread; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestThread { public static String now() { return new SimpleDateFormat("HH:mm:ss").format(new Date()); } public static void log(String msg) { System.out.printf("%s %s %s %n", now() , Thread.currentThread().getName() , msg); } public static void main(String[] args) { Lock lock = new ReentrantLock(); Thread t1 = new Thread() { public void run() { boolean locked = false; try { log("線程啓動"); log("試圖佔有對象:lock"); locked = lock.tryLock(1,TimeUnit.SECONDS); if(locked){ log("佔有對象:lock"); log("進行5秒的業務操做"); Thread.sleep(5000); } else{ log("通過1秒鐘的努力,尚未佔有對象,放棄佔有"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if(locked){ log("釋放對象:lock"); lock.unlock(); } } log("線程結束"); } }; t1.setName("t1"); t1.start(); try { //先讓t1飛2秒 Thread.sleep(2000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } Thread t2 = new Thread() { public void run() { boolean locked = false; try { log("線程啓動"); log("試圖佔有對象:lock"); locked = lock.tryLock(1,TimeUnit.SECONDS); if(locked){ log("佔有對象:lock"); log("進行5秒的業務操做"); Thread.sleep(5000); } else{ log("通過1秒鐘的努力,尚未佔有對象,放棄佔有"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if(locked){ log("釋放對象:lock"); lock.unlock(); } } log("線程結束"); } }; t2.setName("t2"); t2.start(); } }
步驟 4 : 線程交互
使用synchronized方式進行線程交互,用到的是同步對象的wait,notify和notifyAll方法
Lock也提供了相似的解決辦法,首先經過lock對象獲得一個Condition對象,而後分別調用這個Condition對象的:await, signal,signalAll 方法
注意: 不是Condition對象的wait,nofity,notifyAll方法,是await,signal,signalAll
package multiplethread; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestThread { public static String now() { return new SimpleDateFormat("HH:mm:ss").format(new Date()); } public static void log(String msg) { System.out.printf("%s %s %s %n", now() , Thread.currentThread().getName() , msg); } public static void main(String[] args) { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); Thread t1 = new Thread() { public void run() { try { log("線程啓動"); log("試圖佔有對象:lock"); lock.lock(); log("佔有對象:lock"); log("進行5秒的業務操做"); Thread.sleep(5000); log("臨時釋放對象 lock, 並等待"); condition.await(); log("從新佔有對象 lock,並進行5秒的業務操做"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } finally { log("釋放對象:lock"); lock.unlock(); } log("線程結束"); } }; t1.setName("t1"); t1.start(); try { //先讓t1飛2秒 Thread.sleep(2000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } Thread t2 = new Thread() { public void run() { try { log("線程啓動"); log("試圖佔有對象:lock"); lock.lock(); log("佔有對象:lock"); log("進行5秒的業務操做"); Thread.sleep(5000); log("喚醒等待中的線程"); condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { log("釋放對象:lock"); lock.unlock(); } log("線程結束"); } }; t2.setName("t2"); t2.start(); } }
步驟 5 : 總結Lock和synchronized的區別
Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現,Lock是代碼層面的實現。
Lock能夠選擇性的獲取鎖,若是一段時間獲取不到,能夠放棄。synchronized不行,會一根筋一直獲取下去。 藉助Lock的這個特性,就可以規避死鎖,synchronized必須經過謹慎和良好的設計,才能減小死鎖的發生。
synchronized在發生異常和同步塊結束的時候,會自動釋放鎖。而Lock必須手動釋放, 因此若是忘記了釋放鎖,同樣會形成死鎖。
練習: 多線程 Lock對象
當多個線程按照不一樣順序佔用多個同步對象的時候,就有可能產生死鎖現象。
死鎖之因此會發生,就是由於synchronized 若是佔用不到同步對象,就會苦苦的一直等待下去,藉助tryLock的有限等待時間,解決死鎖問題
答案 :
package multiplethread; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestThread { public static void main(String[] args) throws InterruptedException { Lock lock_ahri = new ReentrantLock(); Lock lock_annie = new ReentrantLock(); Thread t1 = new Thread() { public void run() { // 佔有九尾妖狐 boolean ahriLocked = false; boolean annieLocked = false; try { ahriLocked = lock_ahri.tryLock(10, TimeUnit.SECONDS); if (ahriLocked) { System.out.println("t1 已佔有九尾妖狐"); // 停頓1000秒,另外一個線程有足夠的時間佔有安妮 Thread.sleep(1000); System.out.println("t1 試圖在10秒內佔有安妮"); try { annieLocked = lock_annie.tryLock(10, TimeUnit.SECONDS); if (annieLocked) System.out.println("t1 成功佔有安妮,開始啪啪啪"); else{ System.out.println("t1 總是佔用不了安妮,放棄"); } } finally { if (annieLocked){ System.out.println("t1 釋放安妮"); lock_annie.unlock(); } } } } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } finally { if (ahriLocked){ System.out.println("t1 釋放九尾狐"); lock_ahri.unlock(); } } } }; t1.start(); Thread.sleep(100); Thread t2 = new Thread() { public void run() { boolean annieLocked = false; boolean ahriLocked = false; try {annieLocked = lock_annie.tryLock(10, TimeUnit.SECONDS); if (annieLocked){ System.out.println("t2 已佔有安妮"); // 停頓1000秒,另外一個線程有足夠的時間佔有安妮 Thread.sleep(1000); System.out.println("t2 試圖在10秒內佔有九尾妖狐"); try { ahriLocked = lock_ahri.tryLock(10, TimeUnit.SECONDS); if (ahriLocked) System.out.println("t2 成功佔有九尾妖狐,開始啪啪啪"); else{ System.out.println("t2 總是佔用不了九尾妖狐,放棄"); } } finally { if (ahriLocked){ System.out.println("t2 釋放九尾狐"); lock_ahri.unlock(); } } } } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } finally { if (annieLocked){ System.out.println("t2 釋放安妮"); lock_annie.unlock(); } } } }; t2.start(); } }