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