synchronized關鍵字,Lock對象,阻塞隊列問題

一.   請你談一談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的方式,由於阻塞隊列中不須要手動的去中止/喚醒線程.

相關文章
相關標籤/搜索