多線程

1.實(shí)現(xiàn)多線程

1.1進(jìn)程和線程【理解】

  • 進(jìn)程:是正在運(yùn)行的程序

    ? 是系統(tǒng)進(jìn)行資源分配和調(diào)用的獨(dú)立單位

    ? 每一個(gè)進(jìn)程都有它自己的內(nèi)存空間和系統(tǒng)資源

  • 線程:是進(jìn)程中的單個(gè)順序控制流,是一條執(zhí)行路徑

    ? 單線程:一個(gè)進(jìn)程如果只有一條執(zhí)行路徑,則稱為單線程程序

    ? 多線程:一個(gè)進(jìn)程如果有多條執(zhí)行路徑,則稱為多線程程序

1.2實(shí)現(xiàn)多線程方式一:繼承Thread類【應(yīng)用】

  • 方法介紹

    方法名 說(shuō)明
    void run() 在線程開(kāi)啟后,此方法將被調(diào)用執(zhí)行
    void start() 使此線程開(kāi)始執(zhí)行,Java虛擬機(jī)會(huì)調(diào)用run方法()
  • 實(shí)現(xiàn)步驟

    • 定義一個(gè)類MyThread繼承Thread類
    • 在MyThread類中重寫(xiě)run()方法
    • 創(chuàng)建MyThread類的對(duì)象
    • 啟動(dòng)線程
  • 代碼演示

    public class MyThread extends Thread {
        @Override
        public void run() {
            for(int i=0; i<100; i++) {
                System.out.println(i);
            }
        }
    }
    public class MyThreadDemo {
        public static void main(String[] args) {
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
    
    //        my1.run();
    //        my2.run();
    
            //void start() 導(dǎo)致此線程開(kāi)始執(zhí)行; Java虛擬機(jī)調(diào)用此線程的run方法
            my1.start();
            my2.start();
        }
    }
    
  • 兩個(gè)小問(wèn)題

    • 為什么要重寫(xiě)run()方法?

      因?yàn)閞un()是用來(lái)封裝被線程執(zhí)行的代碼

    • run()方法和start()方法的區(qū)別?

      run():封裝線程執(zhí)行的代碼,直接調(diào)用,相當(dāng)于普通方法的調(diào)用

      start():?jiǎn)?dòng)線程;然后由JVM調(diào)用此線程的run()方法

1.3設(shè)置和獲取線程名稱【應(yīng)用】

  • 方法介紹

    方法名 說(shuō)明
    void setName(String name) 將此線程的名稱更改為等于參數(shù)name
    String getName() 返回此線程的名稱
    Thread currentThread() 返回對(duì)當(dāng)前正在執(zhí)行的線程對(duì)象的引用
  • 代碼演示

    public class MyThread extends Thread {
        public MyThread() {}
        public MyThread(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName()+":"+i);
            }
        }
    }
    public class MyThreadDemo {
        public static void main(String[] args) {
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
    
            //void setName(String name):將此線程的名稱更改為等于參數(shù) name
            my1.setName("高鐵");
            my2.setName("飛機(jī)");
    
            //Thread(String name)
            MyThread my1 = new MyThread("高鐵");
            MyThread my2 = new MyThread("飛機(jī)");
    
            my1.start();
            my2.start();
    
            //static Thread currentThread() 返回對(duì)當(dāng)前正在執(zhí)行的線程對(duì)象的引用
            System.out.println(Thread.currentThread().getName());
        }
    }
    

1.4線程優(yōu)先級(jí)【應(yīng)用】

  • 線程調(diào)度

    • 兩種調(diào)度方式

      • 分時(shí)調(diào)度模型:所有線程輪流使用 CPU 的使用權(quán),平均分配每個(gè)線程占用 CPU 的時(shí)間片
      • 搶占式調(diào)度模型:優(yōu)先讓優(yōu)先級(jí)高的線程使用 CPU,如果線程的優(yōu)先級(jí)相同,那么會(huì)隨機(jī)選擇一個(gè),優(yōu)先級(jí)高的線程獲取的 CPU 時(shí)間片相對(duì)多一些
    • Java使用的是搶占式調(diào)度模型

    • 隨機(jī)性

      假如計(jì)算機(jī)只有一個(gè) CPU,那么 CPU 在某一個(gè)時(shí)刻只能執(zhí)行一條指令,線程只有得到CPU時(shí)間片,也就是使用權(quán),才可以執(zhí)行指令。所以說(shuō)多線程程序的執(zhí)行是有隨機(jī)性,因?yàn)檎l(shuí)搶到CPU的使用權(quán)是不一定的

  • 優(yōu)先級(jí)相關(guān)方法

    方法名 說(shuō)明
    final int getPriority() 返回此線程的優(yōu)先級(jí)
    final void setPriority(int newPriority) 更改此線程的優(yōu)先級(jí) 線程默認(rèn)優(yōu)先級(jí)是5;線程優(yōu)先級(jí)的范圍是:1-10
  • 代碼演示

    public class ThreadPriority extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ":" + i);
            }
        }
    }
    public class ThreadPriorityDemo {
        public static void main(String[] args) {
            ThreadPriority tp1 = new ThreadPriority();
            ThreadPriority tp2 = new ThreadPriority();
            ThreadPriority tp3 = new ThreadPriority();
    
            tp1.setName("高鐵");
            tp2.setName("飛機(jī)");
            tp3.setName("汽車(chē)");
    
            //public final int getPriority():返回此線程的優(yōu)先級(jí)
            System.out.println(tp1.getPriority()); //5
            System.out.println(tp2.getPriority()); //5
            System.out.println(tp3.getPriority()); //5
    
            //public final void setPriority(int newPriority):更改此線程的優(yōu)先級(jí)
    //        tp1.setPriority(10000); //IllegalArgumentException
            System.out.println(Thread.MAX_PRIORITY); //10
            System.out.println(Thread.MIN_PRIORITY); //1
            System.out.println(Thread.NORM_PRIORITY); //5
    
            //設(shè)置正確的優(yōu)先級(jí)
            tp1.setPriority(5);
            tp2.setPriority(10);
            tp3.setPriority(1);
    
            tp1.start();
            tp2.start();
            tp3.start();
        }
    }
    

1.5線程控制【應(yīng)用】

  • 相關(guān)方法

    方法名 說(shuō)明
    static void sleep(long millis) 使當(dāng)前正在執(zhí)行的線程停留(暫停執(zhí)行)指定的毫秒數(shù)
    void join() 等待這個(gè)線程死亡
    void setDaemon(boolean on) 將此線程標(biāo)記為守護(hù)線程,當(dāng)運(yùn)行的線程都是守護(hù)線程時(shí),Java虛擬機(jī)將退出
  • 代碼演示

    sleep演示:
    public class ThreadSleep extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ":" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public class ThreadSleepDemo {
        public static void main(String[] args) {
            ThreadSleep ts1 = new ThreadSleep();
            ThreadSleep ts2 = new ThreadSleep();
            ThreadSleep ts3 = new ThreadSleep();
    
            ts1.setName("曹操");
            ts2.setName("劉備");
            ts3.setName("孫權(quán)");
    
            ts1.start();
            ts2.start();
            ts3.start();
        }
    }
    
    Join演示:
    public class ThreadJoin extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ":" + i);
            }
        }
    }
    public class ThreadJoinDemo {
        public static void main(String[] args) {
            ThreadJoin tj1 = new ThreadJoin();
            ThreadJoin tj2 = new ThreadJoin();
            ThreadJoin tj3 = new ThreadJoin();
    
            tj1.setName("康熙");
            tj2.setName("四阿哥");
            tj3.setName("八阿哥");
    
            tj1.start();
            try {
                tj1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            tj2.start();
            tj3.start();
        }
    }
    
    Daemon演示:
    public class ThreadDaemon extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ":" + i);
            }
        }
    }
    public class ThreadDaemonDemo {
        public static void main(String[] args) {
            ThreadDaemon td1 = new ThreadDaemon();
            ThreadDaemon td2 = new ThreadDaemon();
    
            td1.setName("關(guān)羽");
            td2.setName("張飛");
    
            //設(shè)置主線程為劉備
            Thread.currentThread().setName("劉備");
    
            //設(shè)置守護(hù)線程
            td1.setDaemon(true);
            td2.setDaemon(true);
    
            td1.start();
            td2.start();
    
            for(int i=0; i<10; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
    

1.6線程的生命周期【理解】

? 線程一共有五種狀態(tài),線程在各種狀態(tài)之間轉(zhuǎn)換。

[圖片上傳失敗...(image-2ba3af-1604492765214)]

1.7實(shí)現(xiàn)多線程方式二:實(shí)現(xiàn)Runnable接口【應(yīng)用】

  • Thread構(gòu)造方法

    方法名 說(shuō)明
    Thread(Runnable target) 分配一個(gè)新的Thread對(duì)象
    Thread(Runnable target, String name) 分配一個(gè)新的Thread對(duì)象
  • 實(shí)現(xiàn)步驟

    • 定義一個(gè)類MyRunnable實(shí)現(xiàn)Runnable接口
    • 在MyRunnable類中重寫(xiě)run()方法
    • 創(chuàng)建MyRunnable類的對(duì)象
    • 創(chuàng)建Thread類的對(duì)象,把MyRunnable對(duì)象作為構(gòu)造方法的參數(shù)
    • 啟動(dòng)線程
  • 代碼演示

    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            for(int i=0; i<100; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
    public class MyRunnableDemo {
        public static void main(String[] args) {
            //創(chuàng)建MyRunnable類的對(duì)象
            MyRunnable my = new MyRunnable();
    
            //創(chuàng)建Thread類的對(duì)象,把MyRunnable對(duì)象作為構(gòu)造方法的參數(shù)
            //Thread(Runnable target)
    //        Thread t1 = new Thread(my);
    //        Thread t2 = new Thread(my);
            //Thread(Runnable target, String name)
            Thread t1 = new Thread(my,"高鐵");
            Thread t2 = new Thread(my,"飛機(jī)");
    
            //啟動(dòng)線程
            t1.start();
            t2.start();
        }
    }
    
  • 多線程的實(shí)現(xiàn)方案有兩種

    • 繼承Thread類
    • 實(shí)現(xiàn)Runnable接口
  • 相比繼承Thread類,實(shí)現(xiàn)Runnable接口的好處

    • 避免了Java單繼承的局限性

    • 適合多個(gè)相同程序的代碼去處理同一個(gè)資源的情況,把線程和程序的代碼、數(shù)據(jù)有效分離,較好的體現(xiàn)了面向?qū)ο蟮脑O(shè)計(jì)思想

2.線程同步

2.1賣(mài)票【應(yīng)用】

  • 案例需求

    某電影院目前正在上映國(guó)產(chǎn)大片,共有100張票,而它有3個(gè)窗口賣(mài)票,請(qǐng)?jiān)O(shè)計(jì)一個(gè)程序模擬該電影院賣(mài)票

  • 實(shí)現(xiàn)步驟

    • 定義一個(gè)類SellTicket實(shí)現(xiàn)Runnable接口,里面定義一個(gè)成員變量:private int tickets = 100;

    • 在SellTicket類中重寫(xiě)run()方法實(shí)現(xiàn)賣(mài)票,代碼步驟如下

    • 判斷票數(shù)大于0,就賣(mài)票,并告知是哪個(gè)窗口賣(mài)的

    • 賣(mài)了票之后,總票數(shù)要減1

    • 票沒(méi)有了,也可能有人來(lái)問(wèn),所以這里用死循環(huán)讓賣(mài)票的動(dòng)作一直執(zhí)行

    • 定義一個(gè)測(cè)試類SellTicketDemo,里面有main方法,代碼步驟如下

    • 創(chuàng)建SellTicket類的對(duì)象

    • 創(chuàng)建三個(gè)Thread類的對(duì)象,把SellTicket對(duì)象作為構(gòu)造方法的參數(shù),并給出對(duì)應(yīng)的窗口名稱

    • 啟動(dòng)線程

  • 代碼實(shí)現(xiàn)

    public class SellTicket implements Runnable {
        private int tickets = 100;
        //在SellTicket類中重寫(xiě)run()方法實(shí)現(xiàn)賣(mài)票,代碼步驟如下
        @Override
        public void run() {
            while (true) {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");
                    tickets--;
                }
            }
        }
    }
    public class SellTicketDemo {
        public static void main(String[] args) {
            //創(chuàng)建SellTicket類的對(duì)象
            SellTicket st = new SellTicket();
    
            //創(chuàng)建三個(gè)Thread類的對(duì)象,把SellTicket對(duì)象作為構(gòu)造方法的參數(shù),并給出對(duì)應(yīng)的窗口名稱
            Thread t1 = new Thread(st,"窗口1");
            Thread t2 = new Thread(st,"窗口2");
            Thread t3 = new Thread(st,"窗口3");
    
            //啟動(dòng)線程
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
  • 執(zhí)行結(jié)果

    [圖片上傳失敗...(image-81215a-1604492765214)]

2.2賣(mài)票案例的問(wèn)題【理解】

  • 賣(mài)票出現(xiàn)了問(wèn)題

    • 相同的票出現(xiàn)了多次

    • 出現(xiàn)了負(fù)數(shù)的票

  • 問(wèn)題產(chǎn)生原因

    線程執(zhí)行的隨機(jī)性導(dǎo)致的

    public class SellTicket implements Runnable {
        private int tickets = 100;
    
        @Override
        public void run() {
            //相同的票出現(xiàn)了多次
    //        while (true) {
    //            //tickets = 100;
    //            //t1,t2,t3
    //            //假設(shè)t1線程搶到CPU的執(zhí)行權(quán)
    //            if (tickets > 0) {
    //                //通過(guò)sleep()方法來(lái)模擬出票時(shí)間
    //                try {
    //                    Thread.sleep(100);
    //                    //t1線程休息100毫秒
    //                    //t2線程搶到了CPU的執(zhí)行權(quán),t2線程就開(kāi)始執(zhí)行,執(zhí)行到這里的時(shí)候,t2線程休息100毫秒
    //                    //t3線程搶到了CPU的執(zhí)行權(quán),t3線程就開(kāi)始執(zhí)行,執(zhí)行到這里的時(shí)候,t3線程休息100毫秒
    //                } catch (InterruptedException e) {
    //                    e.printStackTrace();
    //                }
    //                //假設(shè)線程按照順序醒過(guò)來(lái)
    //                //t1搶到CPU的執(zhí)行權(quán),在控制臺(tái)輸出:窗口1正在出售第100張票
    //                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");
    //                //t2搶到CPU的執(zhí)行權(quán),在控制臺(tái)輸出:窗口2正在出售第100張票
    //                //t3搶到CPU的執(zhí)行權(quán),在控制臺(tái)輸出:窗口3正在出售第100張票
    //                tickets--;
    //                //如果這三個(gè)線程還是按照順序來(lái),這里就執(zhí)行了3次--的操作,最終票就變成了97
    //            }
    //        }
    
            //出現(xiàn)了負(fù)數(shù)的票
            while (true) {
                //tickets = 1;
                //t1,t2,t3
                //假設(shè)t1線程搶到CPU的執(zhí)行權(quán)
                if (tickets > 0) {
                    //通過(guò)sleep()方法來(lái)模擬出票時(shí)間
                    try {
                        Thread.sleep(100);
                        //t1線程休息100毫秒
                        //t2線程搶到了CPU的執(zhí)行權(quán),t2線程就開(kāi)始執(zhí)行,執(zhí)行到這里的時(shí)候,t2線程休息100毫秒
                        //t3線程搶到了CPU的執(zhí)行權(quán),t3線程就開(kāi)始執(zhí)行,執(zhí)行到這里的時(shí)候,t3線程休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //假設(shè)線程按照順序醒過(guò)來(lái)
                    //t1搶到了CPU的執(zhí)行權(quán),在控制臺(tái)輸出:窗口1正在出售第1張票
                    //假設(shè)t1繼續(xù)擁有CPU的執(zhí)行權(quán),就會(huì)執(zhí)行tickets--;操作,tickets = 0;
                    //t2搶到了CPU的執(zhí)行權(quán),在控制臺(tái)輸出:窗口1正在出售第0張票
                    //假設(shè)t2繼續(xù)擁有CPU的執(zhí)行權(quán),就會(huì)執(zhí)行tickets--;操作,tickets = -1;
                    //t3搶到了CPU的執(zhí)行權(quán),在控制臺(tái)輸出:窗口3正在出售第-1張票
                    //假設(shè)t2繼續(xù)擁有CPU的執(zhí)行權(quán),就會(huì)執(zhí)行tickets--;操作,tickets = -2;
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");
                    tickets--;
                }
            }
        }
    }
    

2.3同步代碼塊解決數(shù)據(jù)安全問(wèn)題【應(yīng)用】

  • 安全問(wèn)題出現(xiàn)的條件

    • 是多線程環(huán)境

    • 有共享數(shù)據(jù)

    • 有多條語(yǔ)句操作共享數(shù)據(jù)

  • 如何解決多線程安全問(wèn)題呢?

    • 基本思想:讓程序沒(méi)有安全問(wèn)題的環(huán)境
  • 怎么實(shí)現(xiàn)呢?

    • 把多條語(yǔ)句操作共享數(shù)據(jù)的代碼給鎖起來(lái),讓任意時(shí)刻只能有一個(gè)線程執(zhí)行即可

    • Java提供了同步代碼塊的方式來(lái)解決

  • 同步代碼塊格式:

    synchronized(任意對(duì)象) { 
      多條語(yǔ)句操作共享數(shù)據(jù)的代碼 
    }
    

    synchronized(任意對(duì)象):就相當(dāng)于給代碼加鎖了,任意對(duì)象就可以看成是一把鎖

  • 同步的好處和弊端

    • 好處:解決了多線程的數(shù)據(jù)安全問(wèn)題

    • 弊端:當(dāng)線程很多時(shí),因?yàn)槊總€(gè)線程都會(huì)去判斷同步上的鎖,這是很耗費(fèi)資源的,無(wú)形中會(huì)降低程序的運(yùn)行效率

  • 代碼演示

    public class SellTicket implements Runnable {
        private int tickets = 100;
        private Object obj = new Object();
    
        @Override
        public void run() {
            while (true) {
                //tickets = 100;
                //t1,t2,t3
                //假設(shè)t1搶到了CPU的執(zhí)行權(quán)
                //假設(shè)t2搶到了CPU的執(zhí)行權(quán)
                synchronized (obj) {
                    //t1進(jìn)來(lái)后,就會(huì)把這段代碼給鎖起來(lái)
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                            //t1休息100毫秒
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //窗口1正在出售第100張票
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");
                        tickets--; //tickets = 99;
                    }
                }
                //t1出來(lái)了,這段代碼的鎖就被釋放了
            }
        }
    }
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            SellTicket st = new SellTicket();
    
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

2.4同步方法解決數(shù)據(jù)安全問(wèn)題【應(yīng)用】

  • 同步方法的格式

    同步方法:就是把synchronized關(guān)鍵字加到方法上

    修飾符 synchronized 返回值類型 方法名(方法參數(shù)) { 
      方法體;
    }
    

    同步方法的鎖對(duì)象是什么呢?

    ? this

  • 靜態(tài)同步方法

    同步靜態(tài)方法:就是把synchronized關(guān)鍵字加到靜態(tài)方法上

    修飾符 static synchronized 返回值類型 方法名(方法參數(shù)) { 
      方法體;
    }
    

    同步靜態(tài)方法的鎖對(duì)象是什么呢?

    ? 類名.class

  • 代碼演示

    public class SellTicket implements Runnable {
        private static int tickets = 100;
        private int x = 0;
    
        @Override
        public void run() {
            while (true) {
              sellTicket();
          }
        }
    //    同步方法
    //    private synchronized void sellTicket() {
    //        if (tickets > 0) {
    //            try {
    //                Thread.sleep(100);
    //            } catch (InterruptedException e) {
    //                e.printStackTrace();
    //            }
    //            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");
    //            tickets--;
    //        }
    //    }
        
    //  靜態(tài)同步方法
        private static synchronized void sellTicket() {
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");
                tickets--;
            }
        }
    }
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            SellTicket st = new SellTicket();
    
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

2.5線程安全的類【理解】

  • StringBuffer

    • 線程安全,可變的字符序列

    • 從版本JDK 5開(kāi)始,被StringBuilder 替代。 通常應(yīng)該使用StringBuilder類,因?yàn)樗С炙邢嗤牟僮?,但它更快,因?yàn)樗粓?zhí)行同步

  • Vector

    • 從Java 2平臺(tái)v1.2開(kāi)始,該類改進(jìn)了List接口,使其成為Java Collections Framework的成員。 與新的集合實(shí)現(xiàn)不同, Vector被同步。 如果不需要線程安全的實(shí)現(xiàn),建議使用ArrayList代替Vector
  • Hashtable

    • 該類實(shí)現(xiàn)了一個(gè)哈希表,它將鍵映射到值。 任何非null對(duì)象都可以用作鍵或者值
    • 從Java 2平臺(tái)v1.2開(kāi)始,該類進(jìn)行了改進(jìn),實(shí)現(xiàn)了Map接口,使其成為Java Collections Framework的成員。 與新的集合實(shí)現(xiàn)不同, Hashtable被同步。 如果不需要線程安全的實(shí)現(xiàn),建議使用HashMap代替Hashtable

2.6Lock鎖【應(yīng)用】

雖然我們可以理解同步代碼塊和同步方法的鎖對(duì)象問(wèn)題,但是我們并沒(méi)有直接看到在哪里加上了鎖,在哪里釋放了鎖,為了更清晰的表達(dá)如何加鎖和釋放鎖,JDK5以后提供了一個(gè)新的鎖對(duì)象Lock

Lock是接口不能直接實(shí)例化,這里采用它的實(shí)現(xiàn)類ReentrantLock來(lái)實(shí)例化

  • ReentrantLock構(gòu)造方法

    方法名 說(shuō)明
    ReentrantLock() 創(chuàng)建一個(gè)ReentrantLock的實(shí)例
  • 加鎖解鎖方法

    方法名 說(shuō)明
    void lock() 獲得鎖
    void unlock() 釋放鎖
  • 代碼演示

    public class SellTicket implements Runnable {
        private int tickets = 100;
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                try {
                    lock.lock();
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");
                        tickets--;
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    public class SellTicketDemo {
        public static void main(String[] args) {
            SellTicket st = new SellTicket();
    
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

3.生產(chǎn)者消費(fèi)者

3.1生產(chǎn)者和消費(fèi)者模式概述【應(yīng)用】

  • 概述

    生產(chǎn)者消費(fèi)者模式是一個(gè)十分經(jīng)典的多線程協(xié)作的模式,弄懂生產(chǎn)者消費(fèi)者問(wèn)題能夠讓我們對(duì)多線程編程的理解更加深刻。

    所謂生產(chǎn)者消費(fèi)者問(wèn)題,實(shí)際上主要是包含了兩類線程:

    ? 一類是生產(chǎn)者線程用于生產(chǎn)數(shù)據(jù)

    ? 一類是消費(fèi)者線程用于消費(fèi)數(shù)據(jù)

    為了解耦生產(chǎn)者和消費(fèi)者的關(guān)系,通常會(huì)采用共享的數(shù)據(jù)區(qū)域,就像是一個(gè)倉(cāng)庫(kù)

    生產(chǎn)者生產(chǎn)數(shù)據(jù)之后直接放置在共享數(shù)據(jù)區(qū)中,并不需要關(guān)心消費(fèi)者的行為

    消費(fèi)者只需要從共享數(shù)據(jù)區(qū)中去獲取數(shù)據(jù),并不需要關(guān)心生產(chǎn)者的行為

    [圖片上傳失敗...(image-779763-1604492765214)]

  • Object類的等待和喚醒方法

    方法名 說(shuō)明
    void wait() 導(dǎo)致當(dāng)前線程等待,直到另一個(gè)線程調(diào)用該對(duì)象的 notify()方法或 notifyAll()方法
    void notify() 喚醒正在等待對(duì)象監(jiān)視器的單個(gè)線程
    void notifyAll() 喚醒正在等待對(duì)象監(jiān)視器的所有線程

3.2生產(chǎn)者和消費(fèi)者案例【應(yīng)用】

  • 案例需求

    生產(chǎn)者消費(fèi)者案例中包含的類:

    奶箱類(Box):定義一個(gè)成員變量,表示第x瓶奶,提供存儲(chǔ)牛奶和獲取牛奶的操作

    生產(chǎn)者類(Producer):實(shí)現(xiàn)Runnable接口,重寫(xiě)run()方法,調(diào)用存儲(chǔ)牛奶的操作

    消費(fèi)者類(Customer):實(shí)現(xiàn)Runnable接口,重寫(xiě)run()方法,調(diào)用獲取牛奶的操作

    測(cè)試類(BoxDemo):里面有main方法,main方法中的代碼步驟如下

    ①創(chuàng)建奶箱對(duì)象,這是共享數(shù)據(jù)區(qū)域

    ②創(chuàng)建消費(fèi)者創(chuàng)建生產(chǎn)者對(duì)象,把奶箱對(duì)象作為構(gòu)造方法參數(shù)傳遞,因?yàn)樵谶@個(gè)類中要調(diào)用存儲(chǔ)牛奶的操作

    ③對(duì)象,把奶箱對(duì)象作為構(gòu)造方法參數(shù)傳遞,因?yàn)樵谶@個(gè)類中要調(diào)用獲取牛奶的操作

    ④創(chuàng)建2個(gè)線程對(duì)象,分別把生產(chǎn)者對(duì)象和消費(fèi)者對(duì)象作為構(gòu)造方法參數(shù)傳遞

    ⑤啟動(dòng)線程

  • 代碼實(shí)現(xiàn)

    public class Box {
        //定義一個(gè)成員變量,表示第x瓶奶
        private int milk;
        //定義一個(gè)成員變量,表示奶箱的狀態(tài)
        private boolean state = false;
    
        //提供存儲(chǔ)牛奶和獲取牛奶的操作
        public synchronized void put(int milk) {
            //如果有牛奶,等待消費(fèi)
            if(state) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            //如果沒(méi)有牛奶,就生產(chǎn)牛奶
            this.milk = milk;
            System.out.println("送奶工將第" + this.milk + "瓶奶放入奶箱");
    
            //生產(chǎn)完畢之后,修改奶箱狀態(tài)
            state = true;
    
            //喚醒其他等待的線程
            notifyAll();
        }
    
        public synchronized void get() {
            //如果沒(méi)有牛奶,等待生產(chǎn)
            if(!state) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            //如果有牛奶,就消費(fèi)牛奶
            System.out.println("用戶拿到第" + this.milk + "瓶奶");
    
            //消費(fèi)完畢之后,修改奶箱狀態(tài)
            state = false;
    
            //喚醒其他等待的線程
            notifyAll();
        }
    }
    
    public class Producer implements Runnable {
        private Box b;
    
        public Producer(Box b) {
            this.b = b;
        }
    
        @Override
        public void run() {
            for(int i=1; i<=30; i++) {
                b.put(i);
            }
        }
    }
    
    public class Customer implements Runnable {
        private Box b;
    
        public Customer(Box b) {
            this.b = b;
        }
    
        @Override
        public void run() {
            while (true) {
                b.get();
            }
        }
    }
    
    public class BoxDemo {
        public static void main(String[] args) {
            //創(chuàng)建奶箱對(duì)象,這是共享數(shù)據(jù)區(qū)域
            Box b = new Box();
    
            //創(chuàng)建生產(chǎn)者對(duì)象,把奶箱對(duì)象作為構(gòu)造方法參數(shù)傳遞,因?yàn)樵谶@個(gè)類中要調(diào)用存儲(chǔ)牛奶的操作
            Producer p = new Producer(b);
            //創(chuàng)建消費(fèi)者對(duì)象,把奶箱對(duì)象作為構(gòu)造方法參數(shù)傳遞,因?yàn)樵谶@個(gè)類中要調(diào)用獲取牛奶的操作
            Customer c = new Customer(b);
    
            //創(chuàng)建2個(gè)線程對(duì)象,分別把生產(chǎn)者對(duì)象和消費(fèi)者對(duì)象作為構(gòu)造方法參數(shù)傳遞
            Thread t1 = new Thread(p);
            Thread t2 = new Thread(c);
    
            //啟動(dòng)線程
            t1.start();
            t2.start();
        }
    }
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容