進程的並發執行(入門)


1、實現多線程

1.1 進程

進程:是正在運行的程序,是系統進行資源分配和調用的獨立單位

每一個進程都有它自己的內存空間和系統資源

1.2 線程

線程:是進程中的單個順序控制流,是一條執行路徑
單線程:一個進程如果只有一條執行路徑,則稱為單線程程序
多線程:一個進程如果有多條執行路徑,則稱為多線程程序

1.3 多線程的實現方式

方式1:繼承Thread類

  1. 定義一個類MyThread繼承Thread類
  2. 在MyThread類中重寫run()方法
  3. 創建MyThread類的對象
  4. 啟動線程
// 定義一個類MyThread繼承Thread類
public class MyThread extends Thread {
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    // 在MyThread類中重寫run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName()+":"+i);
        }
    }
}

public class MyThreadDemo {
    public static void main(String[] args) {
        // 創建MyThread類的對象
        MyThread thread1 = new MyThread();
        // run()並沒有真正啟動線程,run()是封裝線程執行的代碼,直接調用,相當於普通方法的調用
        // thread1.run();

        // 啟動線程
        // void start() 導致此線程開始執行; Java虛擬機調用此線程的run方法
        thread1.start();
    }
}

兩個小問題:

  1. 為什么要重寫run()方法?

    ​ 因為run()是用來封裝被線程執行的代碼

  2. run()方法和start()方法的區別?

    ​ run():封裝線程執行的代碼,直接調用,相當於普通方法的調用

    ​ start():啟動線程;然后由JVM調用此線程的run()方法

方式2:實現Runnable接口

  1. 定義一個類MyRunnable實現Runnable接口
  2. 在MyRunnable類中重寫run()方法
  3. 創建MyRunnable類的對象
  4. 創建Thread類的對象,把MyRunnable對象作為構造方法的參數
  5. 啟動線程
// 定義一個類MyRunnable實現Runnable接口
public class MyRunnable implements Runnable {
    // 在MyRunnable類中重寫run()方法
    @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) {
        MyRunnable my = new MyRunnable();

        // Thread(Runnable runnable):構造方法,傳入一個實現了Runnable接口的參數
        Thread t1 = new Thread(my);
        Thread t2 = new Thread(my);

        // 啟動線程
        t1.start();
        t2.start();
    }
}

實現Runnable接口的好處

  1. 避免了Java單繼承的局限性
  2. 適合多個相同程序的代碼去處理同一個資源的情況,把線程和程序的代碼、數據有效分離,較好的體現了面向對象的設計思想

1.4 設置和獲取線程名稱

Thread類中設置和獲取線程名稱的方法

  • void setName(String name):將此線程的名稱更改為等於參數name
  • String getName():返回此線程的名稱
  • 通過構造方法也可以設置線程名稱
public class MyThread extends Thread {
    public MyThread(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}

public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("劉備");
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();

        // 這里調用的是Thread類中的setName方法
        mt2.setName("關羽");
        mt3.setName("張飛");

        mt2.start();
        mt3.start();
    }
}

如何獲取main()方法所在的線程名稱

  • static Thread currentThread():返回當前正在執行的線程對象的引用
public class MyThreadDemo {
    public static void main(String[] args) {
        // static Thread currentThread():返回當前正在執行的線程對象的引用
        System.out.println(Thread.currentThread().getName());
    }
}

1.5 線程調度

線程有兩種調度模型

  1. 分時調度模型:所有線程輪流使用CPU的使用權,平均分配每個線程占用CPU的時間片
  2. 搶占式調度模型:優先讓優先級高的線程使用CPU,如果線程的優先級相同,那么會隨機選擇一個優先級高的線程獲取的CPU時間片相對多一些

Java使用的是搶占式調度模型

​ 假如計算機只有一個CPU,那么CPU在某一個時刻只能執行一條指令,線程只有得到CPU時間片,也就是使用權,才可以執行指令。所以說多線程程序的執行是有隨機性的,因為誰搶到CPU的使用權是不一定的。

線程優先級

  1. Thread類中設置和獲取線程優先級的方法
    • public final int getPriority():返回此線程的優先級
    • public final void setPriority(int new Priority):更改此線程的優先級
  2. 線程默認優先級是5;線程優先級的范圍是1-10
  3. 線程優先級高僅僅表現線程獲取的CPU時間片的幾率高,但是要在次數比較多,或者多次運行的時候才能看到你想要的效果
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName()+":"+i);
        }
    }
}

public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        MyThread thread3 = new MyThread();

        // 線程默認的優先級為5
        System.out.println(thread1.getPriority()); // 5
        System.out.println(thread2.getPriority()); // 5
        System.out.println(thread3.getPriority()); // 5

        // public final void setPriority(int new Priority):更改此線程的優先級
        // IllegalArgumentException:拋出表示一種方法已經通過了非法或不正確的參數
        // thread1.setPriority(10000);

        // Thread中有三個靜態常量分別表示最高優先級、最低優先級和默認優先級
        System.out.println(Thread.MAX_PRIORITY);// 10
        System.out.println(Thread.MIN_PRIORITY);// 1
        System.out.println(Thread.NORM_PRIORITY);// 5
        // 設置正確的線程優先級
        thread1.setPriority(10);
        thread2.setPriority(1);
        thread3.setPriority(3);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

1.6 線程控制

static void sleep(long millis):使當前正在執行的線程停留(暫停執行)指定的毫秒數

public class ThreadSleep extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
            try {
                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("孫權");
        ts1.start();
        ts2.start();
        ts3.start();
    }
}

void 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();
    }
}

void setDaemon(boolean on):將此線程標記為守護線程,當運行的線程都是守護線程時,JVM將退出

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("關羽");
        td2.setName("張飛");

        Thread thread = Thread.currentThread();
        thread.setName("劉備");
        td1.setDaemon(true);
        td2.setDaemon(true);
        td1.start();
        td2.start();

        for (int i = 0; i < 10; i++) {
            System.out.println(thread.getName()+":"+i);
        }
    }
}

1.7 線程的生命周期

線程的生命周期

2、線程同步

2.1 買票

需求:

  • 某電影院目前正在上映國產大片,共有100張票,而它有3個窗口買票,請設計一個程序模擬該電影院買票

思路:

  • 定義一個SellTicket實現Runnable接口,里面定義一個成員變量private int tickets = 100;

  • 在SellTicket類中重寫run()方法實現賣票,代碼步驟如下

    • 判斷票數大於0,就賣票,並告知是哪個窗口賣的
    • 賣了票之后,總票數要減一
    • 票沒有了,也有可能有人來問,所以這里用死循環讓賣票動作一直執行
  • 定義一個測試類SellTicketDemo,里面有main方法,代碼執行如下

    • 創建SellTicket類的對象

    • 創建三個Thread類的對象,把SellTicket對象作為構造方法的參數,並給出對應的窗口名

    • 啟動線程
public class SellTicket implements Runnable {
    private int tickets = 100;
    @Override
    public void run() {
        while (true) {
            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 s = new SellTicket();
        Thread t1 = new Thread(s,"一號窗口");
        Thread t2 = new Thread(s,"二號窗口");
        Thread t3 = new Thread(s,"三號窗口");

        t1.start();
        t2.start();
        t3.start();
    }
}

買票出現了問題

相同的票出現了多次

@Override
public void run() {
    // 出現了相同的票
    while (true) {
        // tickets = 100;
        // t1,t2,t3
        // 假設t1線程搶到CPU的執行權
        if (tickets> 0) {
            // 通過sleep()方法來模擬出票時間
            try {
                Thread.sleep(100);
                // t1線程休息100毫秒
                // t2線程搶到了CPU的執行權,t2線程就開始執行,執行到這里時,t2線程休息100毫秒
                // t3線程搶到了CPU的執行權,t3線程就開始執行,執行到這里時,t3線程休息100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 假設線程按照順序醒過來
            // t1搶到CPU的執行權,在控制台輸出:窗口1正在出售第100張票
            System.out.println(Thread.currentThread().getName() + "正在出售第"
                               +tickets+"張票");
            // t2搶到CPU的執行權,在控制台輸出:窗口2正在出售第100張票
            // t3搶到CPU的執行權,在控制台輸出:窗口3正在出售第100張票
            tickets--;
            // 如果這三個線程還是按照順序來,這里就執行了3次--操作,最終票變為97
        }
    }
}

出現了負數的票

@Override
public void run() {
    // 出現了負數的票
    while (true) {
        // tickets = 100;
        // t1,t2,t3
        // 假設t1線程搶到CPU的執行權
        if (tickets > 0) {
            // 通過sleep()方法來模擬出票時間
            try {
                Thread.sleep(100);
                // t1線程休息100毫秒
                // t2線程搶到了CPU的執行權,t2線程就開始執行,執行到這里時,t2線程休息100毫秒
                // t3線程搶到了CPU的執行權,t3線程就開始執行,執行到這里時,t3線程休息100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 假設線程按照順序醒過來
            // t1搶到了CPU的執行權,在控制台輸出:窗口1正在出售第1張票
            // 假設t1繼續擁有CPU的執行權,就會執行ticket--的操作,ticket= 0
            // t2搶到了CPU的執行權,在控制台輸出:窗口2正在出售第0張票
            // 假設t2繼續擁有CPU的執行權,就會執行ticket--的操作,ticket= -1
            // t3搶到了CPU的執行權,在控制台輸出:窗口3正在出售第-1張票
            // 假設t3繼續擁有CPU的執行權,就會執行ticket--的操作,ticket= -2
            System.out.println(Thread.currentThread().getName() + "正在出售第"
                               +tickets+"張票");
            tickets--;
        }
    }
}

問題原因

線程執行的隨機性導致的

2.2 買票案例數據安全問題

  • 為什么出現問題?(這也是我們判斷多線程程序是否會有數據安全問題的標准)
    • 是否是多線程環境
    • 是否有數據共享
    • 是否有多條語句操作共享數據
  • 如何解決多線程的安全問題?
    • 基本思路:讓程序沒有安全問題的環境
  • 怎么實現呢?
    • 把多條語句操作共享數據的代碼給鎖起來,讓任意時刻只能有一個線程執行即可
    • Java提供了同步代碼塊的方式來解決

2.3 同步代碼塊

鎖多條語句操作共享數據,可以使用同步代碼塊實現
格式:
​ synchronized(任意對象){
​ 多條語句操作共享數據的代碼塊
​ }
synchronized(任意對象):就相當於給代碼加鎖了,任意對象就可以看成是一把鎖

public class SellTicket implements Runnable {
    private int tickets = 100;
    // 這個是鎖對象,可以是任意對象
    // 例如Student s = new Studnent();Integer i = new Integer()都是可以的
    // 只不過在synchronized()中要輸入相同的對象,以代表被synchronized包裹的代碼是被同一把鎖鎖住的
    Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            // 傳入任意對象即可,也可以用匿名類的方法
            // synchronized(new Object()){
            synchronized (obj) {
                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 s = new SellTicket();
        Thread t1 = new Thread(s,"一號窗口");
        Thread t2 = new Thread(s,"二號窗口");
        Thread t3 = new Thread(s,"三號窗口");

        t1.start();
        t2.start();
        t3.start();
    }
}

同步的好處和弊端

  • 好處:解決了多線程的數據安全問題
  • 弊端:當線程很多時,因為每個線程都會去判斷同步上的鎖,很耗費資源,無形中會降低程序的運行效率

2.4 同步方法

同步方法:就是把synchronized關鍵字加到方法上

  • 格式:修飾符 synchronized 返回值類型 方法名(方法參數){...}
public class SellTicket implements Runnable {
    private int tickets = 100;
    private int x = 0;
    @Override
    public void run() {
        while (true) {
            if(x%2 == 0){
                // 這里使用this關鍵字獲取當前調用方法的對象
                // 如果繼續使用object的話會因為用的不是同一把鎖導致沒有同步執行
                synchronized (this) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                           + "正在出售第" + tickets + "張票");
                        tickets--;
                    }
                }
            }else{
                sellTicket();
            }
            x++;
        }
    }
    // 同步代碼鎖的對象是this
    public 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 s = new SellTicket();
        Thread t1 = new Thread(s,"一號窗口");
        Thread t2 = new Thread(s,"二號窗口");
        Thread t3 = new Thread(s,"三號窗口");    
        t1.start();
        t2.start();
        t3.start();
    }
}

同步方法的鎖對象是什么?

  • this

同步靜態方法:就是把synchronized關鍵字加到靜態方法上

  • 格式:修飾符 static synchronized 返回值類型 方法名(方法參數){...}

    public class SellTicket implements Runnable {
        // 靜態方法調用靜態變量,所以講tickets用static修飾符修飾
        private static int tickets = 100;
        private int x = 0;
        @Override
        public void run() {
            while (true) {
                if(x%2 == 0){
                    // 靜態方法的鎖的對象是類名.class
                    synchronized (SellTicket.class) {
                        if (tickets > 0) {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()
                                               + "正在出售第" + tickets + "張票");
                            tickets--;
                        }
                    }
                }else{
                    sellTicket();
                }
                x++;
            }
        }
        // 靜態方法的鎖的對象是類名.class
        public 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 s = new SellTicket();
            Thread t1 = new Thread(s,"一號窗口");
            Thread t2 = new Thread(s,"二號窗口");
            Thread t3 = new Thread(s,"三號窗口");    
            t1.start();
            t2.start();
            t3.start();
        }
    }

同步靜態方法的鎖對象是什么

  • 類名.class

2.5 線程安全的類

StringBuffer

  • 線程安全,可變的字符序列。
  • 從版本JDK5開始,這個類已經被一個等同的類補充了,它被設計為使用一個線程,StringBuilder。通常應該使用StringBuilder類,因為它支持所有相同的操作,但它更快,因為它不執行同步。

Vector

  • 從Java2平台v1.2開始,該類改進了List接口,使其成為Java Collections Framework的成員。與新的集合實現不同,Vector被同步。如果不需要線程安全的實現,建議使用ArrayList代替Vector

Hashtable

  • 該類實現了一個哈希表,它將鍵映射到值。任何非null對象都可以用作鍵值或值。
  • 從Java 2平台v1.2開始,該類進行了改進,實現了Map接口,成為Java Collections Framework的成員。與新的集合實現不同,Hashtable被同步。如果不需線程安全的實現,建議用HashMap代替Hashtable。
/*
    線程安全的類
        StringBuffer
        Vector
        Hashtable
 */
public class ThreadDemo {
    public static void main(String[] args) {
        // 一般線程安全類中只用StringBuffer,因為ArrayList和HashMap可以通過Collections的指定方法返回一個同步列表
        StringBuffer sb = new StringBuffer();


        List<String> list = Collections.synchronizedList(new ArrayList<String>());
        Vector<String> v = new Vector<>();

        Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());
        Hashtable<String, String> ht = new Hashtable<>();
    }
}

2.6 Lock鎖

雖然我們可以理解同步代碼塊和同步方法的鎖對象問題,但是我們並沒有直接看到在哪里加上了鎖,在哪里釋放了鎖

為了更清晰的表達如何加鎖和釋放鎖,JDK5以后提供了一個新的鎖對象Lock

Lock實現提供比使用synchronized方法和語句可以獲得更廣泛的鎖定操作

  • Lock中提供了獲得鎖和釋放鎖的方法
    • void lock():獲得鎖
    • void unlock():釋放鎖

Lock是接口,不能直接實例化,這里采用它的實現類ReentrantLock來實例化

  • Reentrantlock的構造方法
    • ReentrantLock():創建一個ReentrantLock的實例

因為代碼運行中可能會出現異常,導致不能釋放鎖,所以使用try{}finally{}語句塊

public class SellTicket implements Runnable {
    private static int tickets = 100;
    // ReentrantLock():創建一個ReentrantLock的實例
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // void lock():獲得鎖
                lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() 
                                       + "正在出售第" + tickets + "張票");
                    tickets--;
                }
            } finally {
                // void unlock():釋放鎖
                lock.unlock();
            }

        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket s = new SellTicket();
        Thread t1 = new Thread(s,"一號窗口");
        Thread t2 = new Thread(s,"二號窗口");
        Thread t3 = new Thread(s,"三號窗口");

        t1.start();
        t2.start();
        t3.start();
    }
}

3、生產者消費者

3.1 生產者消費者模式概述

生產者消費者模式是一個十分經典的多線程協作的模式,弄懂生產者消費者問題能夠讓我們對多線程編程的理解更加深刻

所謂生產者消費者問題,實際上主要是包含了兩類線程:

  • 一類是生產者線程用於生產數據
  • 一類是消費者線程用於消費數據

為了解耦生產者和消費者的關系,通常會采用共享的數據區域,就像是一個倉庫

  • 生產者生產數據之后直接放置在共享數據區中,並不需要關心消費者的行為
  • 消費者只需要從共享數據區中去獲取數據,並不需要關心生產者的行為
    • 生產者----→共享數據區域←----消費者

為了體現生產和消費過程中的等待和喚醒,Java就提供了幾個方法供我們使用,這幾個方法在Object類中

Object類的等待和喚醒方法:

  • void wait() 導致當前線程等待,直到另一個線程調用該對象的notify()方法或notifyAll()方法
  • void notify() 喚醒正在等待對象監視器的單個線程。
  • void notifyAll() 喚醒正在等待對象監視器的所有線程。

3.2 生產着消費者案例

生產者消費者案例中包含的類:

  1. 奶箱類(Box):定義一個成員變量,表示第X瓶奶,提供存儲牛奶和獲取牛奶的操作
  2. 生產者類(Producer):實現Runnable接口,重寫run()方法,調用存儲牛奶的操作
  3. 消費者類(Customer):實現Runnable接口,重寫run()方法,調用獲取牛奶的操作
  4. 測試類(BoxDemo):里面有main方法,main方法中的代碼步驟如下
    • 創建奶箱對象,這是共享數據區域
    • 創建消費者對象,把奶箱對象作為構造方法參數傳遞,因為在這個類中要調用獲取牛奶的操作
    • 創建兩個線程對象,分別把生產者對象和消費者對象作為構造方法參數傳遞
    • 啟動線程
// 1.奶箱類(Box):定義一個成員變量,表示第X瓶奶,提供存儲牛奶和獲取牛奶的操作
public class Box {
    private int milk = 0;
    private static final int MAX_NUM = 50;
    private static final int MIN_NUM = 0;

    // IllegalMonitorStateException不擁有指定的監視器,方法不用synchronized修飾時拋出
    // 需要添加同步關鍵字synchronized
    // 寫了等待還要寫喚醒
    public synchronized void getMilk() {
        if (milk<=MIN_NUM) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("消費者拿到第" + milk + "瓶奶");
        milk--;
        // 不寫喚醒最多各自執行一次之后,兩條進程全部進入等待狀態
        notify();
    }

    public synchronized void setMilk() {
        if (milk>=MAX_NUM) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        milk++;
        System.out.println("生產者將第" + milk + "瓶奶放入奶箱");
        notify();
    }
}

// 2.生產者類(Producer):實現Runnable接口,重寫run()方法,調用存儲牛奶的操作
public class Producer implements Runnable {

    Box box;

    public Producer() {
    }

    public Producer(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            box.setMilk();
        }
    }
}

// 3.消費者類(Customer):實現Runnable接口,重寫run()方法,調用獲取牛奶的操作
public class Customer implements Runnable {

    Box box;

    public Customer() {
    }

    public Customer(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        while (true) {
            box.getMilk();
        }
    }
}

public class BoxDemo {
    public static void main(String[] args) {
        Box box = new Box();
        Producer p = new Producer(box);
        Customer c = new Customer(box);
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);

        t1.start();
        t2.start();
    }
}

小結:

  • 共享數據區域的數據要進行監控,否則消費者會無限制的從共享數據區域拿到數據致使數據變成負數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM