1. 程式人生 > 其它 >golang定義type為func 做一個回撥 - 解耦NewClient與Option的實現

golang定義type為func 做一個回撥 - 解耦NewClient與Option的實現

1.java預設有兩個執行緒:
    1.main執行緒
    2.GC垃圾回收執行緒
    
2.java真的可以開啟執行緒麼?
    答案是否定的,其實底層本地去呼叫是c++的方法,因為java是執行在虛擬機器上的,無法操作硬體!
    原理如下:
        public synchronized void start() {
            if (threadStatus != 0)
                throw new IllegalThreadStateException();
            group.add(this);
            boolean started = false;
            try {
                //重點:執行緒內部呼叫的是start0的本地方法
                start0();
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                }
            }
        }
        //呼叫的其實是內部的start0方法
        private native void start0();
        
3.併發、並行
     3.1 併發:多個執行緒操作同一個資源
         CPU 一核情況下,模擬出來多條執行緒,快速交替,營造出共同進行的假象!
     3.2 並行: 
         CPU 多核情況下,多個執行緒可以同時執行!
     
     程式獲取cpu核數:
         System.out.println("cpu核數:"+
             Runtime.getRuntime().availableProcessors()
         );
     如何提高執行效率呢:
         1.併發:CPU資源的充分利用
         2.並行:執行緒池
         多執行緒在多核cpu下的執行速率快於單執行緒,因為多核cpu下,多個執行緒是同時執行的,
         但是單核cpu下,多執行緒的執行速率慢於單執行緒的速率,因為單核下,多執行緒是交替進行,會有上下文的切換!
 
 結論:
     1.單核CPU下,多執行緒不能實際的提高程式執行效率,只是為了能夠在不同的任務間切換,不同執行緒輪流使用CPU,
         會涉及到下上文切換,會損失一部分的速率
     2.多核cpu可以並行跑多個執行緒,但是是否能夠提升效率還得分情況:
         2.1 有的任務:經過精心設計,將任務拆分,並行執行,當然可以提高程式得執行效率,但是不是所有得任務都能拆分
     3.IO操作不佔用CPU,只是我門一半拷貝檔案使用的是【阻塞IO】,這時相當於執行緒雖然不用cpu,但需要一直等待IO結束,沒能充分利用執行緒
       後面會有【非阻塞IO】和【非同步IO優化】
     

1.執行緒的狀態

總計6狀態:
     1. NEW(新建)
     2. RUNNABLE(執行)
     3. BLOCKED(阻塞)
     4. WAITING(等待)
     5. TIMED_WAITING(超時等待,超時後就過)
     6. TERMINATED(終止)
 
 1.wait和sleep的區別
     1.1 來自不同的類
         wait-->Object
         sleep--->Thread
     1.2 關於鎖的釋放
         wait:會釋放鎖,  sleep:不會釋放鎖
     1.3 使用的範圍:
         wait--->只能在同步程式碼塊中使用
         sleep--->可以在任意位置上使用
     1.4 是否需要捕獲異常
         wait:不需要捕獲異常
         sleep:必須要捕獲異常

2.Lock鎖(重點)

傳統的synchronized
一個簡單的場景:售票
    重點:
        1.併發就是多個執行緒操作同一個資源,資源類儘量是一個純粹的類,不和其他做耦合,之前的是在資源類上實現runable介面,這樣不太好!
        2.lambda表示式的使用:
示例如下:
    public class SellTicket {
        public static void main(String[] args) {
            /*
                1.併發:多個執行緒操作同一個資源類,把資源丟入到執行緒中
                2.lambda表示式:(請求引數)->{程式碼}
             */
    
            Ticket ticket=new Ticket();
            new Thread(()->{
                    for(int i =0;i<=50;i++){
                        ticket.sellTicket();
                    }
                },"A").start();
            // 重點2:不使用lambda表示式,定義匿名內部類,並實現其run方法
            //發現lambda表示式是省略了匿名內部類命和方法名
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i =0;i<=50;i++){
                        ticket.sellTicket();
                    }
                }
            }, "B").start();
            new Thread(()->{
                for(int i =0;i<=50;i++){
                    ticket.sellTicket();
                }
            },"C").start();
        }
    }
    //重點3:純粹的資源類,不和任何做耦合,不實現runable介面
    class Ticket{
        private int num=50;
        //重點4:方法上加鎖,可以按照預期賣票
        public synchronized void sellTicket(){
            if(num>0){
                System.out.println(Thread.currentThread().getName()+"賣票!剩餘票:"+num--);
            }
        }
    }
上述例子使用synchronized實現了鎖,那如何根據Lock介面實現鎖呢?
由上述截圖可知,Lock介面有三個實現類:
    1.ReentrantLock(普通鎖-->最常用)
    2.ReentrantReadWriteLock.ReadLock(讀寫鎖-->讀鎖)
    3.ReentrantReadWriteLock.WriteLock(讀寫鎖-->寫鎖)

具體的實現程式碼如下:
    public class Juc_Test_Lock {
        public static void main(String[] args) {
            int num=50;
            Ticket ticket=new Ticket(num);
            new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"A").start();
            new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"B").start();
            new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"C").start();
        }
    }
    @AllArgsConstructor
    @NoArgsConstructor
    class Ticket{
        public Ticket(int ticketNum) {
            this.ticketNum = ticketNum;
        }
        private int ticketNum;
        //重點1:可重入鎖的使用
        Lock lock=new ReentrantLock();
        public  void sell(){
            //重點2:步驟1:加鎖
            lock.lock();
            try {
                //步驟2:trycatch寫業務邏輯
                if(ticketNum>0)
                    System.out.println(Thread.currentThread().getName()+"售票:剩餘-->"+ticketNum--);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //重點3:步驟3:解鎖
                lock.unlock();
            }
        }
    }


Lock lock=new ReentrantLock();程式碼細究:底層程式碼如下:
    
    {
        構造器1:發現建立的是非公平鎖
        public ReentrantLock() {
            sync = new NonfairSync();
        }
        構造器2:傳入引數,建立對應的鎖,預設是非公平鎖
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    }

1.公平鎖FairSync:不能插隊,排隊進行
2.非公平鎖NonfairSync: 可以插隊,預設都是非公平鎖(包括synchronized )


重點:synchronized和lock鎖的區別
    1.synchronized是內建關鍵字,Lock是java類
    2.synchronized無法判斷獲取鎖的狀態,Lock可以判斷是否獲取鎖
    3.synchronized會自動會釋放鎖,Lock必須要手動釋放鎖,要不會造成死鎖
    4.synchronized(執行緒1獲取鎖,執行緒2等待,即使執行緒1阻塞);
      Lock鎖就不一定會等待下去!
    5.synchronized 可重入鎖,不可以中斷,非公平
      Lock,可重入鎖,可以判斷鎖,非公平(可以自己設定)
    6.synchronized:適合鎖少量的同步程式碼,
      Lock適合鎖大量的同步程式碼

問題:鎖是什麼?如何判斷鎖的是誰?    
    1.物件(可能是多個)
    2.Class(只能是一個)

生產者和消費者的synchronized 版本以及虛假喚醒問題

先看一個場景:
    一個賣面的麵館,有一個做面的廚師和一個吃麵的食客,需要保證,廚師做一碗麵,食客吃一碗麵,
    不能一次性多做幾碗面,更不能沒有面的時候吃麵;
    按照上述操作,進行十輪做面吃麵的操作。
樣例程式碼:
    public class Juc_test {
        public static void main(String[] args){
            Noodels noodels=new Noodels();
            //重點1:建立兩個執行緒:1廚子造面   2.食客吃麵,先做10碗
            new Thread(()->{
                try {
                    for (int i=0;i<=10;i++){
                        noodels.makeNoodeles();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"廚子").start();
            new Thread(()->{
                try {
                    for (int i=0;i<=10;i++){
                        noodels.eatNoodeles();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"食客").start();
        }
    }
    class Noodels{
        //面的數量
        private int num=0;
        /*
            做面方法
         */
        public synchronized  void makeNoo生產者和消費者的synchronized 版本以及虛假喚醒問題deles() throws InterruptedException {
            if(num>0){
                this.wait();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"造面了!當前面數:"+num);
            //面已做好,喚醒食客
            this.notifyAll();
        }
    
        public synchronized void eatNoodeles() throws InterruptedException {
            if (num==0){
                this.wait();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"造面了!當前面數:"+num);
            this.notifyAll();
        }
    }
執行結果如下:很滿意,達到了效果,做一碗吃一碗的水平!
    廚子造面了!當前面數:1
    食客造面了!當前面數:0
    廚子造面了!當前面數:1
    食客造面了!當前面數:0
    廚子造面了!當前面數:1
    食客造面了!當前面數:0
    廚子造面了!當前面數:1


問題來了:如果多個執行緒呢,即兩個廚子,兩個食客呢??改造程式碼,只需要在測試類裡多加幾個執行緒!
    new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"廚子A").start();
    new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"廚子B).start();
    new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"食客A").start();
    new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"食客B").start();
執行結果:打破了吃一碗造一碗的邏輯了,並且明明加了synchronized鎖,為什麼會這樣呢?這裡就涉及到了執行緒的虛假喚醒!
    廚子A造面了!當前面數:1
    食客A造面了!當前面數:0
    廚子B造面了!當前面數:1
    廚子A造面了!當前面數:2
    廚子B造面了!當前面數:3
    ...

虛假喚醒

上面的問題就是"虛假喚醒"。 當我們只有一個廚師一個食客時,只能是廚師做面或者食客吃麵,並沒有其他情況; 但是當有兩個廚師,兩個食客時,就會出現下面的問題:

1.初始狀態

2.廚師A得到操作權,發現面的數量為0,可以做面,面的份數+1,然後喚醒所有執行緒;

3.廚師B得到操作權,發現面的數量為1,不可以做面,執行wait操作;

4.廚師A得到操作權,發現面的數量為1,不可以做面,執行wait操作;

5.食客甲得到操作權,發現面的數量為1,可以吃麵,吃完麵後面的數量-1,並喚醒所有執行緒;

6.此時廚師A得到操作權了,因為是從剛才阻塞的地方繼續執行,就不用再判斷面的數量是否為0了,所以直接面的數量+1,並喚醒其他執行緒;

7.此時廚師B得到操作權了,因為是從剛才阻塞的地方繼續執行,就不用再判斷面的數量是否為0了,所以直接面的數量+1,並喚醒其他執行緒;

    if(num != 0){
         this.wait();
    }
出現虛假喚醒的原因:
    是從阻塞態到就緒態再到執行態沒有進行判斷(num的判斷),直接進行下述的+1或-1,
    所以我們只需要讓其每次得到操作權時都進行判斷就可以了;
解決辦法:將if判斷改為while,每次都會判斷,喚醒以後都先判斷條件是否成立!
    while(num != 0){
         this.wait();
    }

JUC版的生產者和消費者

使用Lock和Condition實現上述生產者和消費者問題:
    樣例程式碼:
        class Noodels{
            //面的數量
            private int num=0;
            //重點1:獲取lock鎖(可重入鎖)
            Lock lock = new ReentrantLock();
            //重點2:獲取condition物件
            Condition condition = lock.newCondition();
            /*
                做面方法
             */
            public void makeNoodeles() throws InterruptedException {
                //重點3:加鎖,不用synchronized關鍵字
                lock.lock();
                try {
                    while (num>0){
                        //重點4:使用condition.await來阻塞佇列
                        condition.await();
                    }
                    num++;
                    System.out.println(Thread.currentThread().getName()+"造面了!當前面數:"+num);
                    //重點5:condition.signalAll();來解鎖
                    condition.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //重點5:解鎖
                    lock.unlock();
                }
            }
        
            public  void eatNoodeles() throws InterruptedException {
                lock.lock();
                try {
                    while (num==0){
                        condition.await();
                    }
                    num--;
                    System.out.println(Thread.currentThread().getName()+"造面了!當前面數:"+num);
                    condition.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
        
問題:如何精確喚醒呢?例如A,B,C,D四個執行緒,如何精確的執行順序A->B->C->D
    實現如下:    
    /**
     * Bruk.liu
     * A執行完呼叫B,B執行完呼叫C,C執行完呼叫A
     */
    public class C {
        public static void main(String[] args) {
            Data3 data = new Data3();
     
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    data.printA();
                }
            },"A").start();
            
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    data.printB();
                }
            },"B").start();
            
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    data.printC();
                }
            },"C").start();
        }
    }
     
    //資源類
    class Data3{
        //建立Lock鎖
       private Lock lock = new ReentrantLock();
        //同步監視器,建立三個監視器
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();
        private int number = 1;//1就是A執行,2就是B執行
     
       public void printA(){
            lock.lock();
           try {
               while(number != 1){
                   //等待
                   condition1.await();
               }
               System.out.println(Thread.currentThread().getName()+"===>AAAAAAAAA");
               //喚醒指定執行緒
                number = 2;
               //呼叫指定監視器,喚醒指定執行緒
               condition2.signal();
           } catch (Exception e) {
               e.printStackTrace();
           } finally {
               lock.unlock();
           }
       }
     
        public void printB(){
            lock.lock();
            try {
                while(number != 2){
                    //等待
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName()+"===>BBBBBBBB");
                //喚醒指定執行緒
                number = 3;
                condition3.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
     
        public void printC(){
            lock.lock();
            try {
                while(number != 3){
                    //等待
                    condition3.await();
                }
                System.out.println(Thread.currentThread().getName()+"===>CCCCCCCCC");
                //喚醒指定執行緒
                number = 1;
                condition1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

8鎖現象

1.synchronized鎖的物件是方法呼叫者!
2.static synchronized...鎖的是class這個類!
示例如下:
1.兩種方法都sendMessage/call都加上鎖synchronized,鎖的是方法的呼叫者!
        public class Juc_Test_Lock {
            public static void main(String[] args) throws InterruptedException {
               //同一個資源物件Phone,所以所的是同一資源物件phone
               Phone phone=new Phone();
               new Thread(()->{phone.sendMessage();},"發簡訊!").start();
               //重點1:兩個執行緒間休眠4秒,能明顯看出哪個執行緒先執行
               TimeUnit.SECONDS.sleep(1);;
               new Thread(()->{phone.call();},"打電話!").start();
            }
        }
        class Phone{
            //重點2:方法加鎖,並在列印前加入休眠,能明顯看出哪個先執行!
            public synchronized void sendMessage(){
                try {
                    TimeUnit.SECONDS.sleep(4);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("傳送簡訊");
            }
            public synchronized void call(){
                System.out.println("打電話!");
            }
        }
    輸出:因為兩個方法都加了synchronized,鎖的是方法的呼叫者,方法呼叫者是同一物件phone,所以鎖是同一把鎖!
        傳送簡訊
        打電話!

2.兩個物件:
    建立兩個物件,因為synchronized鎖的是方法呼叫者,此處是 phone1和phone2,不是同一物件,所以執行緒2不會等待執行緒1執行完畢
        Phone phone1=new Phone();
        Phone phone2=new Phone();
        new Thread(()->{phone1.sendMessage();},"發簡訊!").start();
        TimeUnit.SECONDS.sleep(1);;
        new Thread(()->{phone2.call();},"打電話!").start();
    結論:
        打電話!   
        傳送簡訊 

3.static synchronized:鎖定的是這個類模板
    程式碼示例如下:
        public class Juc_Test_Lock {
            public static void main(String[] args) throws InterruptedException {
               Phone phone=new Phone();
               new Thread(()->{phone.sendMessage();},"發簡訊!").start();
               TimeUnit.SECONDS.sleep(1);;
               new Thread(()->{phone.call();},"打電話!").start();
            }
        }
        class Phone{
            重點1:方法上使用static synchronized修飾
            public static synchronized void sendMessage(){
                try {
                    TimeUnit.SECONDS.sleep(4);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("傳送簡訊");
            }
            public static synchronized void call(){
                System.out.println("打電話!");
            }
        }
    輸出:static synchronized:鎖定的是這個類模板,執行緒2必須等待執行緒1執行完畢後才能執行!因為這是一把鎖
        傳送簡訊
        打電話!

4.上述情況下,方法加了static synchronized,但是是兩個物件
    重點1:建立了兩個物件去執行static synchronized修飾的方法
        Phone phone1=new Phone();
        Phone phone2=new Phone();
        new Thread(()->{phone1.sendMessage();},"發簡訊!").start();
        TimeUnit.SECONDS.sleep(1);;
        new Thread(()->{phone2.call();},"打電話!").start();
    輸出:發現執行緒2必須等執行緒1執行完畢後才執行,因為static synchronized鎖的是類模板,是一般鎖!
        傳送簡訊
        打電話!

5.staic synchronized和synchronized混用:
    public class Juc_Test_Lock {
        public static void main(String[] args) throws InterruptedException {
           重點1:單個物件 
           Phone phone=new Phone();
           new Thread(()->{phone.sendMessage();},"發簡訊!").start();
           TimeUnit.SECONDS.sleep(1);;
           new Thread(()->{phone.call();},"打電話!").start();
        }
    }
    class Phone{
        重點2:使用了static synchronized
        public static synchronized void sendMessage(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("傳送簡訊");
        }
        重點3:使用synchronized
        public synchronized void call(){
            System.out.println("打電話!");
        }
    }
    輸出:因為staic synchronized和synchronized鎖的是不同的物件,synchronized鎖的是方法呼叫者phone物件,而staic synchronized鎖的是Phnoe類模板
    所以是兩把不同的鎖,所以執行緒2不會等待1執行結束才執行!
        打電話!
        傳送簡訊