1. 程式人生 > >多執行緒、等待喚醒機制、經典案例【總結】

多執行緒、等待喚醒機制、經典案例【總結】

一、多執行緒
1.等待喚醒機制案例

//包子類
        public class BaoZi {
            //皮
            String pi;
            //陷
            String xian;
            //包子的狀態: 有 true,沒有 false,設定初始值為false沒有包子
            boolean flag = false;
        }


        //生產者
        public class BaoZiPu extends Thread{
            //1.需要在成員位置建立一個包子變數
private BaoZi bz; //2.使用帶引數構造方法,為這個包子變數賦值 public BaoZiPu(BaoZi bz) { this.bz = bz; } //設定執行緒任務(run):生產包子 @Override public void run() { //定義一個變數 int count = 0; //讓包子鋪一直生產包子
while(true){ //必須同時同步技術保證兩個執行緒只能有一個在執行 synchronized (bz){ //對包子的狀態進行判斷 if(bz.flag==true){ //包子鋪呼叫wait方法進入等待狀態 try { bz.wait(); } catch
(InterruptedException e) { e.printStackTrace(); } } //被喚醒之後執行,包子鋪生產包子 //增加一些趣味性:交替生產兩種包子 if(count%2==0){ //生產 薄皮三鮮餡包子 bz.pi = "薄皮"; bz.xian = "三鮮餡"; }else{ //生產 冰皮 牛肉大蔥陷 bz.pi = "冰皮"; bz.xian = "牛肉大蔥陷"; } count++; System.out.println("包子鋪正在生產:"+bz.pi+bz.xian+"包子"); //生產包子需要3秒鐘 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //包子鋪生產好了包子 //修改包子的狀態為true有 bz.flag = true; //喚醒吃貨執行緒,讓吃貨執行緒吃包子 bz.notify(); System.out.println("包子鋪已經生產好了:"+bz.pi+bz.xian+"包子,吃貨可以開始吃了"); } } } } //消費者 public class ChiHuo extends Thread{ //1.需要在成員位置建立一個包子變數 private BaoZi bz; //2.使用帶引數構造方法,為這個包子變數賦值 public ChiHuo(BaoZi bz) { this.bz = bz; } //設定執行緒任務(run):吃包子 @Override public void run() { //使用死迴圈,讓吃貨一直吃包子 while (true){ //必須同時同步技術保證兩個執行緒只能有一個在執行 synchronized (bz){ //對包子的狀態進行判斷 if(bz.flag==false){ //吃貨呼叫wait方法進入等待狀態 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //被喚醒之後執行的程式碼,吃包子 System.out.println("吃貨正在吃:"+bz.pi+bz.xian+"的包子"); //吃貨吃完包子 //修改包子的狀態為false沒有 bz.flag = false; //吃貨喚醒包子鋪執行緒,生產包子 bz.notify(); System.out.println("吃貨已經把:"+bz.pi+bz.xian+"的包子吃完了,包子鋪開始生產包子"); System.out.println("----------------------------------------------------"); } } } } //測試類 public class Demo { public static void main(String[] args) { //建立包子物件; BaoZi bz =new BaoZi(); //建立包子鋪執行緒,開啟,生產包子; new BaoZiPu(bz).start(); //建立吃貨執行緒,開啟,吃包子; new ChiHuo(bz).start(); } }
    案例二:有100個包,分別在官網和實體店賣出,使用等待喚醒機制
//Bag類
        public class Bag {
            private int num;

            public Bag(int num) {
                this.num = num;
            }

            public int getNum() {
                return num;
            }

            public void setNum(int num) {
                this.num = num;
            }
        }
        //實體店類實現Runnable介面
        public class ReallyStore implements Runnable{
            private Bag b;

            public ReallyStore(Bag b) {
                this.b = b;
            }

            @Override
            public void run() {
                while(true) {
                    synchronized (b)
                    {
                        if(b.getNum() == 0)
                        {
                            b.notifyAll();
                            break;
                        }
                        b.notify();

                        int num = b.getNum();   //獲取包的數量
                        System.out.println(Thread.currentThread().getName() + "正在賣出第" + (100 - num + 1) + "個包包,還剩餘" + ( --num));
                        b.setNum(num);   //將變化後的寶的數量重新set
                        try {
                            b.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }
            //網點類實現Runnable 其實程式碼和上面實體店類裡面的程式碼是一樣的
            public class Web implements Runnable{

                private Bag b;

                public Web(Bag b) {
                    this.b = b;
                }

                @Override
                public void run() {
                    while (true)
                    {
                        synchronized (b)
                        {
                            if(b.getNum() == 0)    //當包包的數量為0的時候就結束迴圈
                            {
                                b.notifyAll();
                                break;
                            }
                            b.notify();

                            int num = b.getNum();   //獲取包的數量
                            System.out.println(Thread.currentThread().getName() + "正在賣出第" + (100 - num + 1) + "個包包,還剩餘" + ( --num));
                            b.setNum(num);   //將變化後的寶的數量重新set
                            try {
                                b.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }

                //測試類
                public class Test {
                    public static void main(String[] args) {
                        Bag b = new Bag(100);
                        new Thread(new Web(b),"官網").start();
                        new Thread(new Web(b),"時地點").start();
                    }
                }

2.執行緒池
    執行緒池就是一個包含多個執行緒的一個容器
    當有任務需要用到執行緒的時候,從容器取出來進行使用。用完以後再把執行緒歸還給池中
    //執行緒類
    public class RunnableImpl implements Runnable{
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"建立了一個新的執行緒執行");
            }
        }


        //測試類
        public class Demo01ThreadPool {
            public static void main(String[] args) {
                //1.使用執行緒池的工廠類Executors裡邊提供的靜態方法newFixedThreadPool生產一個指定執行緒數量的執行緒池
                ExecutorService es = Executors.newFixedThreadPool(2);
                //3.呼叫ExecutorService中的方法submit,傳遞執行緒任務(實現類),開啟執行緒,執行run方法
                es.submit(new RunnableImpl());//pool-1-thread-1建立了一個新的執行緒執行
                //執行緒池會一直開啟,使用完了執行緒,會自動把執行緒歸還給執行緒池,執行緒可以繼續使用
                es.submit(new RunnableImpl());//pool-1-thread-1建立了一個新的執行緒執行
                es.submit(new RunnableImpl());//pool-1-thread-2建立了一個新的執行緒執行

                //4.呼叫ExecutorService中的方法shutdown銷燬執行緒池(不建議執行)
                //es.shutdown();

                //es.submit(new RunnableImpl());//拋異常,執行緒池都沒有了,就不能獲取執行緒了
            }
        }
二、Lambda表示式
    1.定義格式
        (引數):代表要執行的介面中的某個方法,如果有引數就傳遞。如果沒有小括號中空著
        ->:將上面的小括號中的引數傳遞給方法內部
        {}:重寫方法後要執行的程式碼

    2.Lambda表示式的練習-無引數無返回值
//介面
        public interface Cook {
            //定義無引數無返回值的方法makeFood
            public abstract void makeFood();
        }

        //測試類
        public class Demo01Cook {
            public static void main(String[] args) {
                //使用Lambda表示式,簡化匿名內部類的書寫
                invokeCook(()->{
                    System.out.println("吃飯了");
                });
            }

            //定義一個方法,引數傳遞Cook介面,方法內部呼叫Cook介面中的方法makeFood
            public static void invokeCook(Cook cook){
                cook.makeFood();
            }
        }
3.Lambda表示式的練習-有引數有返回值
    public class Demo01Arrays {
            public static void main(String[] args) {
                //使用陣列儲存多個Person物件
                Person[] arr = {
                        new Person("柳巖",38),
                        new Person("迪麗熱巴",18),
                        new Person("古力娜扎",19)
                };

                //使用Lambda表示式,簡化匿名內部類
                Arrays.sort(arr,(Person o1, Person o2)->{
                    return o1.getAge()-o2.getAge();
                });

                //優化省略Lambda
                //Arrays.sort(arr,(o1, o2)->o1.getAge()-o2.getAge());

                //遍歷陣列
                for (Person p : arr) {
                    System.out.println(p);
                }
            }
        }
4.Lambda表示式的練習-自定義介面
    //介面
        public interface Calculator {
            //定義一個計算兩個int整數和的方法並返回結果
            public abstract int calc(int a,int b);
        }

        //測試類
        public class Demo01Calculator {
            public static void main(String[] args) {
                //使用Lambda表示式簡化匿名內部類的書寫
                invokeCalc(120,130,(int a,int b)->{
                    return a + b;
                });

                //優化省略Lambda
                //invokeCalc(120,130,(a,b)-> a + b);
            }

            /*
                定義一個方法
                引數傳遞兩個int型別的整數
                引數傳遞Calculator介面
                方法內部呼叫Calculator中的方法calc計算兩個整數的和
             */
            public static void invokeCalc(int a,int b,Calculator c){
                int sum = c.calc(a,b);
                System.out.println(sum);
            }
        }
5.使用Lambda表示式的前提和省略規則
    使用前提:
        A:必須是介面
        B:介面中只能有一個抽象方法
    省略格式:
        Lambda表示式:是可推導,可以省略
        凡是根據上下文推匯出來的內容,都可以省略書寫
        可以省略的內容:
            1.(引數列表):括號中引數列表的資料型別,可以省略不寫
            2.(引數列表):括號中的引數如果只有一個,那麼型別和()都可以省略
            3.{一些程式碼}:如果{}中的程式碼只有一行,無論是否有返回值,都可以省略({},return,分號)
                注意:要省略{},return,分號必須一起省略


6.約束介面中只能有一個抽象方法的註解
    @FunctionalInterface   將一個介面規範為函式式介面