一. 請你談一談synchronized和lock有什麼區別?java
1.synchronized是java的關鍵字,屬於jvm層面,底層是經過moninter對象實現的.Lock是具體的接口,屬於api層面.api
2.synchronized不須要用戶去手動釋放鎖,當synchronized的代碼執行完成後,系統會自動釋放線程對鎖的佔用,Lock多線程
則須要用戶去手動釋放鎖,若是沒有主動去釋放鎖,就會致使死鎖的發生.jvm
3.synchronized不可被中斷,除非程序執行完畢或拋出異常.Lock能夠被中斷(1.設置超時方法 tryLock() 2.lockInterruptibly()this
放代碼塊中,調用interrupt()可中斷)atom
4.synchronized是非公平鎖,Lock默認也是非公平鎖,但能夠經過構造方法,傳入布爾值來肯定鎖的類型.(true 公平鎖,false 非公平鎖)spa
5.synchronized沒有綁定多個條件,Lock能夠分組喚醒須要喚醒的線程,實現精確喚醒,而不像synchronized那樣隨機喚醒一個線程線程
要麼所有喚醒.3d
案例:實現多線程之間的精確調用(實現A->B->C三個線程之間的啓動),code
lock優於synchronized的舉例
要求:A線程打印5次,B線程打印10次,C線程打印15次,緊接着A在打印5次
B打印10次,C打印15次.循環執行10次
class ShareResource{ private int num = 1; // 標誌位 1表示A線程執行,2表示B線程執行,3表示C線程執行 private Lock lock = new ReentrantLock(); private Condition c1 = lock.newCondition(); // A線程執行條件 private Condition c2 = lock.newCondition(); // B線程執行條件 private Condition c3 = lock.newCondition(); // C線程執行條件 public void print5(){ lock.lock(); try { //若是不是A線程執行 while (num != 1){ //A線程等待 c1.await(); } // 切換到B線程 num = 2; // 喚醒B線程 c2.signal(); for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + "\t"+i); } }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } public void print10(){ lock.lock(); try { while (num != 2){ c2.await(); } num = 3; c3.signal(); for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName() + "\t"+i); } }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } public void print15(){ lock.lock(); try { while (num != 3){ c3.await(); } num = 1; c1.signal(); for (int i = 1; i <= 15; i++) { System.out.println(Thread.currentThread().getName() + "\t"+i); } }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } }
public static void main(String[] args) { ShareResource sr = new ShareResource(); //獲取對象 // 執行10次A線程 new Thread(()->{ for (int i = 1; i <= 10; i++) { sr.print5(); } },"A").start(); // 執行10次B線程 new Thread(()->{ for (int i = 1; i <= 10; i++) { sr.print10(); } },"B").start(); // 執行10次C線程 new Thread(()->{ for (int i = 1; i <= 10; i++) { sr.print15(); } },"C").start(); }
執行結果:
A,B,C三個線程依次執行,沒有插隊的狀況,結果就是正確的
二 . 請你談一談生產者/消費者模型,並結合業務,寫一個案例
舉例:多個線程同時操做一份數據,生產者+1,消費者-1
1.使用Lock實現
class ShareData{ private int number = 0; // 資源數據 private Lock lock = new ReentrantLock(); // 鎖對象 private Condition condition = lock.newCondition(); // 條件 /*生產者 +1操做*/ public void increment(){ lock.lock(); try { // 若是生產者已經滿了,就進入等待狀態 // 要使用while防止虛假喚醒(jdk1.8文檔源碼,不能使用if !!!) while (number != 0){ // 等待 condition.await(); } // 業務邏輯 +1 number++; System.out.println(Thread.currentThread().getName() + "\t"+number); //喚醒消費者線程進行-1 condition.signalAll(); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } /*消費者線程 -1操做*/ public void decrement(){ lock.lock(); try { // 若是已經沒有資源了,number = 0 ,等待生產者線程生產 while (number == 0){ condition.await(); } // 業務邏輯 -1 number--; System.out.println(Thread.currentThread().getName() + "\t"+number); // 喚醒生產者線程 condition.signalAll(); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } }
public static void main(String[] args) { ShareData shareData = new ShareData(); // 生產者 new Thread(()->{ for (int i = 1; i <= 5; i++) { shareData.increment(); } },"T1").start(); //消費者 new Thread(()->{ for (int i = 1; i <= 5; i++) { shareData.decrement(); } },"T2").start(); }
執行結果:
生產一個,消費一個,生成一個,消費一個 .......
2. 使用BlockingQueue實現
class ShareMessage{ // 開關,true表示執行,false表示關閉 private volatile boolean FLAG = true; // 原子整型,操做不能被分割 private AtomicInteger atomicInteger = new AtomicInteger(); // 阻塞隊列,用於存放數據 private BlockingQueue<String> blockingQueue = null; // 初始化BlockingQueue 對象的具體的實現類 public ShareMessage(BlockingQueue<String> blockingQueue) { this.blockingQueue = blockingQueue; System.out.println(blockingQueue.getClass().getName()); } /*生產者模型,*/ public void produce() throws InterruptedException{ String data = null; boolean retValue ; while (FLAG){ // 若是開關打開 // 等同於 i++ + "" data = atomicInteger.incrementAndGet() + ""; // 把數據放到阻塞隊列中,並設置超時的時間 retValue = blockingQueue.offer(data,2L, TimeUnit.SECONDS); // 若是放置成功 if(retValue){ System.out.println(Thread.currentThread().getName() + "\t"+ data + "插入隊列成功"); }else { System.out.println(Thread.currentThread().getName() + "\t"+ data + "插入隊列失敗"); } // 讓消費者線程取,方便顯示 TimeUnit.SECONDS.sleep(1); } System.out.println(Thread.currentThread().getName() + "生產結束,FLAG設置爲false"); } /*消費者線程*/ public void consume() throws InterruptedException{ String res = null; while (FLAG){ // 從阻塞隊列中獲取數據 res = blockingQueue.poll(2L,TimeUnit.SECONDS); // 若是沒有獲取到數據, if(null == res || res.equalsIgnoreCase("")){ // 中止生產者線程,並退出當前的消費者線程 FLAG = false; System.out.println(Thread.currentThread().getName()+"\t超過2秒鐘沒有取到數據,消費退出"); return; } System.out.println(Thread.currentThread().getName()+"\t"+res+"消費隊列成功"); } } /*關閉生產/消費模型*/ public void stop(){ this.FLAG = false; } }
public static void main(String[] args) { ShareMessage sm = new ShareMessage(new ArrayBlockingQueue<>(10)); // 啓動生產者 new Thread(()->{ System.out.println(Thread.currentThread().getName()+"生產線程啓動"); try { sm.produce(); } catch (InterruptedException e) { e.printStackTrace(); } },"produce").start(); // 啓動消費者 new Thread(()->{ System.out.println(Thread.currentThread().getName()+"消費線程啓動"); try { sm.consume(); } catch (InterruptedException e) { e.printStackTrace(); } },"consume").start(); // 執行5秒鐘後,中止運行 try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } sm.stop(); }
執行結果:
j結論:
使用阻塞隊列實現生產者/消費者模型更優於使用Lock的方式,由於阻塞隊列中不須要手動的去中止/喚醒線程.