1.線程同步
1.1賣票【應(yīng)用】
-
案例需求
某電影院目前正在上映國產(chǎn)大片,共有100張票,而它有3個(gè)窗口賣票,請(qǐng)?jiān)O(shè)計(jì)一個(gè)程序模擬該電影院賣票
-
實(shí)現(xiàn)步驟
-
定義一個(gè)類SellTicket實(shí)現(xiàn)Runnable接口,里面定義一個(gè)成員變量:private int tickets = 100;
-
在SellTicket類中重寫run()方法實(shí)現(xiàn)賣票,代碼步驟如下
-
判斷票數(shù)大于0,就賣票,并告知是哪個(gè)窗口賣的
-
賣了票之后,總票數(shù)要減1
-
票賣沒了,線程停止
-
定義一個(gè)測試類SellTicketDemo,里面有main方法,代碼步驟如下
-
創(chuàng)建SellTicket類的對(duì)象
-
創(chuàng)建三個(gè)Thread類的對(duì)象,把SellTicket對(duì)象作為構(gòu)造方法的參數(shù),并給出對(duì)應(yīng)的窗口名稱
-
啟動(dòng)線程
-
-
代碼實(shí)現(xiàn)文章來源:http://www.zghlxwxcb.cn/news/detail-713756.html
public class SellTicket implements Runnable { private int tickets = 100; //在SellTicket類中重寫run()方法實(shí)現(xiàn)賣票,代碼步驟如下 @Override public void run() { while (true) { if(ticket <= 0){ //賣完了 break; }else{ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticket + "張票"); } } } } 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(); } }
1.2賣票案例的問題
-
賣票出現(xiàn)了問題
-
相同的票出現(xiàn)了多次
-
出現(xiàn)了負(fù)數(shù)的票
-
-
問題產(chǎn)生原因
線程執(zhí)行的隨機(jī)性導(dǎo)致的,可能在賣票過程中丟失cpu的執(zhí)行權(quán),導(dǎo)致出現(xiàn)問題
1.3同步代碼塊解決數(shù)據(jù)安全問題【應(yīng)用】
-
安全問題出現(xiàn)的條件
-
是多線程環(huán)境
-
有共享數(shù)據(jù)
-
有多條語句操作共享數(shù)據(jù)
-
-
如何解決多線程安全問題呢?
- 基本思想:讓程序沒有安全問題的環(huán)境
-
怎么實(shí)現(xiàn)呢?
-
把多條語句操作共享數(shù)據(jù)的代碼給鎖起來,讓任意時(shí)刻只能有一個(gè)線程執(zhí)行即可
-
Java提供了同步代碼塊的方式來解決
-
-
同步代碼塊格式:
synchronized(任意對(duì)象) { 多條語句操作共享數(shù)據(jù)的代碼 }
synchronized(任意對(duì)象):就相當(dāng)于給代碼加鎖了,任意對(duì)象就可以看成是一把鎖
-
同步的好處和弊端
-
好處:解決了多線程的數(shù)據(jù)安全問題
-
弊端:當(dāng)線程很多時(shí),因?yàn)槊總€(gè)線程都會(huì)去判斷同步上的鎖,這是很耗費(fèi)資源的,無形中會(huì)降低程序的運(yùn)行效率
-
-
代碼演示
public class SellTicket implements Runnable { private int tickets = 100; private Object obj = new Object(); @Override public void run() { while (true) { synchronized (obj) { // 對(duì)可能有安全問題的代碼加鎖,多個(gè)線程必須使用同一把鎖 //t1進(jìn)來后,就會(huì)把這段代碼給鎖起來 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出來了,這段代碼的鎖就被釋放了 } } } 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(); } }
1.4同步方法解決數(shù)據(jù)安全問題【應(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 MyRunnable implements Runnable { private static int ticketCount = 100; @Override public void run() { while(true){ if("窗口一".equals(Thread.currentThread().getName())){ //同步方法 boolean result = synchronizedMthod(); if(result){ break; } } if("窗口二".equals(Thread.currentThread().getName())){ //同步代碼塊 synchronized (MyRunnable.class){ if(ticketCount == 0){ break; }else{ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } ticketCount--; System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticketCount + "張票"); } } } } } private static synchronized boolean synchronizedMthod() { if(ticketCount == 0){ return true; }else{ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } ticketCount--; System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticketCount + "張票"); return false; } } }
public class Demo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
1.5Lock鎖【應(yīng)用】
雖然我們可以理解同步代碼塊和同步方法的鎖對(duì)象問題,但是我們并沒有直接看到在哪里加上了鎖,在哪里釋放了鎖,為了更清晰的表達(dá)如何加鎖和釋放鎖,JDK5以后提供了一個(gè)新的鎖對(duì)象Lock
Lock是接口不能直接實(shí)例化,這里采用它的實(shí)現(xiàn)類ReentrantLock來實(shí)例化
-
ReentrantLock構(gòu)造方法
方法名 說明 ReentrantLock() 創(chuàng)建一個(gè)ReentrantLock的實(shí)例 -
加鎖解鎖方法
方法名 說明 void lock() 獲得鎖 void unlock() 釋放鎖 -
代碼演示
public class Ticket implements Runnable { //票的數(shù)量 private int ticket = 100; private Object obj = new Object(); private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true) { //synchronized (obj){//多個(gè)線程必須使用同一把鎖. try { lock.lock(); if (ticket <= 0) { //賣完了 break; } else { Thread.sleep(100); ticket--; System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticket + "張票"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } // } } } } public class Demo { public static void main(String[] args) { Ticket ticket = new Ticket(); Thread t1 = new Thread(ticket); Thread t2 = new Thread(ticket); Thread t3 = new Thread(ticket); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); } }
1.6死鎖
-
概述
線程死鎖是指由于兩個(gè)或者多個(gè)線程互相持有對(duì)方所需要的資源,導(dǎo)致這些線程處于等待狀態(tài),無法前往執(zhí)行
-
什么情況下會(huì)產(chǎn)生死鎖
- 資源有限
- 同步嵌套
-
代碼演示
public class Demo { public static void main(String[] args) { Object objA = new Object(); Object objB = new Object(); new Thread(()->{ while(true){ synchronized (objA){ //線程一 synchronized (objB){ System.out.println("小康同學(xué)正在走路"); } } } }).start(); new Thread(()->{ while(true){ synchronized (objB){ //線程二 synchronized (objA){ System.out.println("小薇同學(xué)正在走路"); } } } }).start(); } }
2.生產(chǎn)者消費(fèi)者
2.1生產(chǎn)者和消費(fèi)者模式概述【應(yīng)用】
-
概述
生產(chǎn)者消費(fèi)者模式是一個(gè)十分經(jīng)典的多線程協(xié)作的模式,弄懂生產(chǎn)者消費(fèi)者問題能夠讓我們對(duì)多線程編程的理解更加深刻。
所謂生產(chǎn)者消費(fèi)者問題,實(shí)際上主要是包含了兩類線程:
? 一類是生產(chǎn)者線程用于生產(chǎn)數(shù)據(jù)
? 一類是消費(fèi)者線程用于消費(fèi)數(shù)據(jù)
為了解耦生產(chǎn)者和消費(fèi)者的關(guān)系,通常會(huì)采用共享的數(shù)據(jù)區(qū)域,就像是一個(gè)倉庫
生產(chǎn)者生產(chǎn)數(shù)據(jù)之后直接放置在共享數(shù)據(jù)區(qū)中,并不需要關(guān)心消費(fèi)者的行為
消費(fèi)者只需要從共享數(shù)據(jù)區(qū)中去獲取數(shù)據(jù),并不需要關(guān)心生產(chǎn)者的行為
-
Object類的等待和喚醒方法
方法名 說明 void wait() 導(dǎo)致當(dāng)前線程等待,直到另一個(gè)線程調(diào)用該對(duì)象的 notify()方法或 notifyAll()方法 void notify() 喚醒正在等待對(duì)象監(jiān)視器的單個(gè)線程 void notifyAll() 喚醒正在等待對(duì)象監(jiān)視器的所有線程
2.2生產(chǎn)者和消費(fèi)者案例【應(yīng)用】
-
案例需求
-
桌子類(Desk):定義表示包子數(shù)量的變量,定義鎖對(duì)象變量,定義標(biāo)記桌子上有無包子的變量
-
生產(chǎn)者類(Cooker):實(shí)現(xiàn)Runnable接口,重寫run()方法,設(shè)置線程任務(wù)
1.判斷是否有包子,決定當(dāng)前線程是否執(zhí)行
2.如果有包子,就進(jìn)入等待狀態(tài),如果沒有包子,繼續(xù)執(zhí)行,生產(chǎn)包子
3.生產(chǎn)包子之后,更新桌子上包子狀態(tài),喚醒消費(fèi)者消費(fèi)包子
-
消費(fèi)者類(Foodie):實(shí)現(xiàn)Runnable接口,重寫run()方法,設(shè)置線程任務(wù)
1.判斷是否有包子,決定當(dāng)前線程是否執(zhí)行
2.如果沒有包子,就進(jìn)入等待狀態(tài),如果有包子,就消費(fèi)包子
3.消費(fèi)包子后,更新桌子上包子狀態(tài),喚醒生產(chǎn)者生產(chǎn)包子
-
測試類(Demo):里面有main方法,main方法中的代碼步驟如下
創(chuàng)建生產(chǎn)者線程和消費(fèi)者線程對(duì)象
分別開啟兩個(gè)線程
-
-
代碼實(shí)現(xiàn)
public class Desk { //定義一個(gè)標(biāo)記 //true 就表示桌子上有漢堡包的,此時(shí)允許吃貨執(zhí)行 //false 就表示桌子上沒有漢堡包的,此時(shí)允許廚師執(zhí)行 public static boolean flag = false; //漢堡包的總數(shù)量 public static int count = 10; //鎖對(duì)象 public static final Object lock = new Object(); } public class Cooker extends Thread { // 生產(chǎn)者步驟: // 1,判斷桌子上是否有漢堡包 // 如果有就等待,如果沒有才生產(chǎn)。 // 2,把漢堡包放在桌子上。 // 3,叫醒等待的消費(fèi)者開吃。 @Override public void run() { while(true){ synchronized (Desk.lock){ if(Desk.count == 0){ break; }else{ if(!Desk.flag){ //生產(chǎn) System.out.println("廚師正在生產(chǎn)漢堡包"); Desk.flag = true; Desk.lock.notifyAll(); }else{ try { Desk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } } public class Foodie extends Thread { @Override public void run() { // 1,判斷桌子上是否有漢堡包。 // 2,如果沒有就等待。 // 3,如果有就開吃 // 4,吃完之后,桌子上的漢堡包就沒有了 // 叫醒等待的生產(chǎn)者繼續(xù)生產(chǎn) // 漢堡包的總數(shù)量減一 //套路: //1. while(true)死循環(huán) //2. synchronized 鎖,鎖對(duì)象要唯一 //3. 判斷,共享數(shù)據(jù)是否結(jié)束. 結(jié)束 //4. 判斷,共享數(shù)據(jù)是否結(jié)束. 沒有結(jié)束 while(true){ synchronized (Desk.lock){ if(Desk.count == 0){ break; }else{ if(Desk.flag){ //有 System.out.println("吃貨在吃漢堡包"); Desk.flag = false; Desk.lock.notifyAll(); Desk.count--; }else{ //沒有就等待 //使用什么對(duì)象當(dāng)做鎖,那么就必須用這個(gè)對(duì)象去調(diào)用等待和喚醒的方法. try { Desk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } } public class Demo { public static void main(String[] args) { /*消費(fèi)者步驟: 1,判斷桌子上是否有漢堡包。 2,如果沒有就等待。 3,如果有就開吃 4,吃完之后,桌子上的漢堡包就沒有了 叫醒等待的生產(chǎn)者繼續(xù)生產(chǎn) 漢堡包的總數(shù)量減一*/ /*生產(chǎn)者步驟: 1,判斷桌子上是否有漢堡包 如果有就等待,如果沒有才生產(chǎn)。 2,把漢堡包放在桌子上。 3,叫醒等待的消費(fèi)者開吃。*/ Foodie f = new Foodie(); Cooker c = new Cooker(); f.start(); c.start(); } }
2.3生產(chǎn)者和消費(fèi)者案例優(yōu)化【應(yīng)用】
-
需求
- 將Desk類中的變量,采用面向?qū)ο蟮姆绞椒庋b起來
- 生產(chǎn)者和消費(fèi)者類中構(gòu)造方法接收Desk類對(duì)象,之后在run方法中進(jìn)行使用
- 創(chuàng)建生產(chǎn)者和消費(fèi)者線程對(duì)象,構(gòu)造方法中傳入Desk類對(duì)象
- 開啟兩個(gè)線程
-
代碼實(shí)現(xiàn)
public class Desk { //定義一個(gè)標(biāo)記 //true 就表示桌子上有漢堡包的,此時(shí)允許吃貨執(zhí)行 //false 就表示桌子上沒有漢堡包的,此時(shí)允許廚師執(zhí)行 //public static boolean flag = false; private boolean flag; //漢堡包的總數(shù)量 //public static int count = 10; //以后我們?cè)谑褂眠@種必須有默認(rèn)值的變量 // private int count = 10; private int count; //鎖對(duì)象 //public static final Object lock = new Object(); private final Object lock = new Object(); public Desk() { this(false,10); // 在空參內(nèi)部調(diào)用帶參,對(duì)成員變量進(jìn)行賦值,之后就可以直接使用成員變量了 } public Desk(boolean flag, int count) { this.flag = flag; this.count = count; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public Object getLock() { return lock; } @Override public String toString() { return "Desk{" + "flag=" + flag + ", count=" + count + ", lock=" + lock + '}'; } } public class Cooker extends Thread { private Desk desk; public Cooker(Desk desk) { this.desk = desk; } // 生產(chǎn)者步驟: // 1,判斷桌子上是否有漢堡包 // 如果有就等待,如果沒有才生產(chǎn)。 // 2,把漢堡包放在桌子上。 // 3,叫醒等待的消費(fèi)者開吃。 @Override public void run() { while(true){ synchronized (desk.getLock()){ if(desk.getCount() == 0){ break; }else{ //System.out.println("驗(yàn)證一下是否執(zhí)行了"); if(!desk.isFlag()){ //生產(chǎn) System.out.println("廚師正在生產(chǎn)漢堡包"); desk.setFlag(true); desk.getLock().notifyAll(); }else{ try { desk.getLock().wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } } public class Foodie extends Thread { private Desk desk; public Foodie(Desk desk) { this.desk = desk; } @Override public void run() { // 1,判斷桌子上是否有漢堡包。 // 2,如果沒有就等待。 // 3,如果有就開吃 // 4,吃完之后,桌子上的漢堡包就沒有了 // 叫醒等待的生產(chǎn)者繼續(xù)生產(chǎn) // 漢堡包的總數(shù)量減一 //套路: //1. while(true)死循環(huán) //2. synchronized 鎖,鎖對(duì)象要唯一 //3. 判斷,共享數(shù)據(jù)是否結(jié)束. 結(jié)束 //4. 判斷,共享數(shù)據(jù)是否結(jié)束. 沒有結(jié)束 while(true){ synchronized (desk.getLock()){ if(desk.getCount() == 0){ break; }else{ //System.out.println("驗(yàn)證一下是否執(zhí)行了"); if(desk.isFlag()){ //有 System.out.println("吃貨在吃漢堡包"); desk.setFlag(false); desk.getLock().notifyAll(); desk.setCount(desk.getCount() - 1); }else{ //沒有就等待 //使用什么對(duì)象當(dāng)做鎖,那么就必須用這個(gè)對(duì)象去調(diào)用等待和喚醒的方法. try { desk.getLock().wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } } public class Demo { public static void main(String[] args) { /*消費(fèi)者步驟: 1,判斷桌子上是否有漢堡包。 2,如果沒有就等待。 3,如果有就開吃 4,吃完之后,桌子上的漢堡包就沒有了 叫醒等待的生產(chǎn)者繼續(xù)生產(chǎn) 漢堡包的總數(shù)量減一*/ /*生產(chǎn)者步驟: 1,判斷桌子上是否有漢堡包 如果有就等待,如果沒有才生產(chǎn)。 2,把漢堡包放在桌子上。 3,叫醒等待的消費(fèi)者開吃。*/ Desk desk = new Desk(); Foodie f = new Foodie(desk); Cooker c = new Cooker(desk); f.start(); c.start(); } }
2.4阻塞隊(duì)列基本使用
- 阻塞隊(duì)列繼承結(jié)構(gòu)
-
常見BlockingQueue:
ArrayBlockingQueue: 底層是數(shù)組,有界
LinkedBlockingQueue: 底層是鏈表,無界.但不是真正的無界,最大為int的最大值
-
BlockingQueue的核心方法:
put(anObject): 將參數(shù)放入隊(duì)列,如果放不進(jìn)去會(huì)阻塞
take(): 取出第一個(gè)數(shù)據(jù),取不到會(huì)阻塞
-
代碼示例
public class Demo02 { public static void main(String[] args) throws Exception { // 創(chuàng)建阻塞隊(duì)列的對(duì)象,容量為 1 ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1); // 存儲(chǔ)元素 arrayBlockingQueue.put("漢堡包"); // 取元素 System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); // 取不到會(huì)阻塞 System.out.println("程序結(jié)束了"); } }
2.5阻塞隊(duì)列實(shí)現(xiàn)等待喚醒機(jī)制
-
案例需求
-
生產(chǎn)者類(Cooker):實(shí)現(xiàn)Runnable接口,重寫run()方法,設(shè)置線程任務(wù)
1.構(gòu)造方法中接收一個(gè)阻塞隊(duì)列對(duì)象
2.在run方法中循環(huán)向阻塞隊(duì)列中添加包子
3.打印添加結(jié)果
-
消費(fèi)者類(Foodie):實(shí)現(xiàn)Runnable接口,重寫run()方法,設(shè)置線程任務(wù)
1.構(gòu)造方法中接收一個(gè)阻塞隊(duì)列對(duì)象
2.在run方法中循環(huán)獲取阻塞隊(duì)列中的包子
3.打印獲取結(jié)果
-
測試類(Demo):里面有main方法,main方法中的代碼步驟如下
創(chuàng)建阻塞隊(duì)列對(duì)象
創(chuàng)建生產(chǎn)者線程和消費(fèi)者線程對(duì)象,構(gòu)造方法中傳入阻塞隊(duì)列對(duì)象
分別開啟兩個(gè)線程
-
-
代碼實(shí)現(xiàn)
public class Cooker extends Thread { private ArrayBlockingQueue<String> bd; public Cooker(ArrayBlockingQueue<String> bd) { this.bd = bd; } // 生產(chǎn)者步驟: // 1,判斷桌子上是否有漢堡包 // 如果有就等待,如果沒有才生產(chǎn)。 // 2,把漢堡包放在桌子上。 // 3,叫醒等待的消費(fèi)者開吃。 @Override public void run() { while (true) { try { bd.put("漢堡包"); System.out.println("廚師放入一個(gè)漢堡包"); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Foodie extends Thread { private ArrayBlockingQueue<String> bd; public Foodie(ArrayBlockingQueue<String> bd) { this.bd = bd; } @Override public void run() { // 1,判斷桌子上是否有漢堡包。 // 2,如果沒有就等待。 // 3,如果有就開吃 // 4,吃完之后,桌子上的漢堡包就沒有了 // 叫醒等待的生產(chǎn)者繼續(xù)生產(chǎn) // 漢堡包的總數(shù)量減一 //套路: //1. while(true)死循環(huán) //2. synchronized 鎖,鎖對(duì)象要唯一 //3. 判斷,共享數(shù)據(jù)是否結(jié)束. 結(jié)束 //4. 判斷,共享數(shù)據(jù)是否結(jié)束. 沒有結(jié)束 while (true) { try { String take = bd.take(); System.out.println("吃貨將" + take + "拿出來吃了"); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Demo { public static void main(String[] args) { ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1); Foodie f = new Foodie(bd); Cooker c = new Cooker(bd); f.start(); c.start(); } }
后記
????????美好的一天,到此結(jié)束,下次繼續(xù)努力!欲知后續(xù),請(qǐng)看下回分解,寫作不易,感謝大家的支持??! ??????文章來源地址http://www.zghlxwxcb.cn/news/detail-713756.html
到了這里,關(guān)于從零開始學(xué)習(xí) Java:簡單易懂的入門指南之線程同步(三十五)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!