您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關java多線程編程的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
一.相關知識:
Java多線程程序設計到的知識:
(一)對同一個數量進行操作
(二)對同一個對象進行操作
(三)回調方法使用
(四)線程同步,死鎖問題
(五)線程通信
等等
二.示例一:三個售票窗口同時出售20張票;
程序分析:
1.票數要使用同一個靜態值
2.為保證不會出現賣出同一個票數,要java多線程同步鎖。
設計思路:
1.創建一個站臺類Station,繼承Thread,重寫run方法,在run方法里面執行售票操作!售票要使用同步鎖:即有一個站臺賣這張票時,其他站臺要等這張票賣完!
2.創建主方法調用類
(一)創建一個站臺類,繼承Thread
package com.xykj.threadStation; public class Station extends Thread { // 通過構造方法給線程名字賦值 public Station(String name) { super(name);// 給線程名字賦值 } // 為了保持票數的一致,票數要靜態 static int tick = 20; // 創建一個靜態鑰匙 static Object ob = "aa";//值是任意的 // 重寫run方法,實現買票操作 @Override public void run() { while (tick > 0) { synchronized (ob) {// 這個很重要,必須使用一個鎖, // 進去的人會把鑰匙拿在手上,出來后才把鑰匙拿讓出來 if (tick > 0) { System.out.println(getName() + "賣出了第" + tick + "張票"); tick--; } else { System.out.println("票賣完了"); } } try { sleep(1000);//休息一秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
(二)創建主方法調用類
package com.xykj.threadStation; public class MainClass { /** * java多線程同步鎖的使用 * 示例:三個售票窗口同時出售10張票 * */ public static void main(String[] args) { //實例化站臺對象,并為每一個站臺取名字 Station station1=new Station("窗口1"); Station station2=new Station("窗口2"); Station station3=new Station("窗口3"); // 讓每一個站臺對象各自開始工作 station1.start(); station2.start(); station3.start(); } }
程序運行結果:
窗口1賣出了第20張票 窗口2賣出了第19張票 窗口3賣出了第18張票 窗口3賣出了第17張票 窗口1賣出了第16張票 窗口2賣出了第15張票 窗口3賣出了第14張票 窗口1賣出了第13張票 窗口2賣出了第12張票 窗口2賣出了第11張票 窗口1賣出了第10張票 窗口3賣出了第9張票 窗口3賣出了第8張票 窗口1賣出了第7張票 窗口2賣出了第6張票 窗口3賣出了第5張票 窗口1賣出了第4張票 窗口2賣出了第3張票 窗口3賣出了第2張票 窗口1賣出了第1張票 票賣完了
可以看到票數是不會有錯的!
三.示例二:兩個人AB通過一個賬戶A在柜臺取錢和B在ATM機取錢!
程序分析:錢的數量要設置成一個靜態的變量。兩個人要取的同一個對象值
(一)創建一個Bank類
package com.xykj.bank; public class Bank { // 假設一個賬戶有1000塊錢 static int money = 1000; // 柜臺Counter取錢的方法 public void Counter(int money) {// 參數是每次取走的錢 Bank.money -= money;//取錢后總數減少 System.out.println("A取走了" + money + "還剩下" + (Bank.money)); } // ATM取錢的方法 public void ATM(int money) {// 參數是每次取走的錢 Bank.money -= money;//取錢后總數減少 System.out.println("B取走了" + money + "還剩下" + (Bank.money)); } }
(二)創建一個PersonA類
package com.xykj.bank; public class PersonA extends Thread { // 創建銀行對象 Bank bank; // 通過構造器傳入銀行對象,確保兩個人進入的是一個銀行 public PersonA(Bank bank) { this.bank = bank; } //重寫run方法,在里面實現使用柜臺取錢 @Override public void run() { while (Bank.money >= 100) { bank.Counter(100);// 每次取100塊 try { sleep(100);// 取完休息0.1秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
(三)創建一個PersonB類
package com.xykj.bank; public class PersonB extends Thread { // 創建銀行對象 Bank bank; // 通過構造器傳入銀行對象,確保兩個人進入的是一個銀行 public PersonB(Bank bank) { this.bank = bank; } // 重寫run方法,在里面實現使用柜臺取錢 @Override public void run() { while (Bank.money >= 200) { bank.ATM(200);// 每次取200塊 try { sleep(100);// 取完休息0.1秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
(四)創建主方法的調用類
package com.xykj.bank; public class MainClass { /** * 兩個人AB通過一個賬戶A在柜臺取錢和B在ATM機取錢 * */ public static void main(String[] args) { // 實力化一個銀行對象 Bank bank = new Bank(); // 實例化兩個人,傳入同一個銀行的對象 PersonA pA = new PersonA(bank); PersonB pB = new PersonB(bank); // 兩個人開始取錢 pA.start(); pB.start(); } }
運行結果:
可以看到取完就停止運行了。
四.示例三:龜兔賽跑問題
龜兔賽跑:20米//只要為了看到效果,所有距離縮短了
要求:
1.兔子每秒0.5米的速度,每跑2米休息10秒,
2.烏龜每秒跑0.1米,不休息
3.其中一個跑到終點后另一個不跑了!
程序設計思路:
1.創建一個Animal動物類,繼承Thread,編寫一個running抽象方法,重寫run方法,把running方法在run方法里面調用。
2.創建Rabbit兔子類和Tortoise烏龜類,繼承動物類
3.兩個子類重寫running方法
4.本題的第3個要求涉及到線程回調。需要在動物類創建一個回調接口,創建一個回調對象
(一)創建Animal動物類
package com.xykj.rabbit_tortoise; public abstract class Animal extends Thread{ public double length=20;//比賽的長度 public abstract void runing();//抽象方法需要子類實現 //在父類重寫run方法,在子類只要重寫running方法就可以了 @Override public void run() { super.run(); while (length>0) { runing(); } } //在需要回調數據的地方(兩個子類需要),聲明一個接口 public static interface Calltoback{ public void win(); } //2.創建接口對象 public Calltoback calltoback; }
(二)創建Rabbit兔子類
package com.xykj.rabbit_tortoise; public class Rabbit extends Animal { public Rabbit() { setName("兔子");// Thread的方法,給線程賦值名字 } // 重寫running方法,編寫兔子的奔跑操作 @Override public void runing() { // 跑的距離 double dis = 0.5; length -= dis;//跑完后距離減少 if (length <= 0) { length = 0; System.out.println("兔子獲得了勝利"); //給回調對象賦值,讓烏龜不要再跑了 if (calltoback != null) { calltoback.win(); } } System.out.println("兔子跑了" + dis + "米,距離終點還有" + (int)length + "米"); if (length % 2 == 0) {// 兩米休息一次 try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
(三)創建Tortoise烏龜類
package com.xykj.rabbit_tortoise; public class Tortoise extends Animal { public Tortoise() { setName("烏龜");// Thread的方法,給線程賦值名字 } // 重寫running方法,編寫烏龜的奔跑操作 @Override public void runing() { // 跑的距離 double dis = 0.1; length -= dis; if (length <= 0) { length = 0; System.out.println("烏龜獲得了勝利"); // 讓兔子不要在跑了 if (calltoback != null) { calltoback.win(); } } System.out.println("烏龜跑了" + dis + "米,距離終點還有" + (int) length + "米"); try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }
(四)創建一個讓動物線程停止的類,這里要實現回調接口
package com.xykj.rabbit_tortoise; import com.xykj.rabbit_tortoise.Animal.Calltoback; public class LetOneStop implements Calltoback { // 動物對象 Animal an; // 獲取動物對象,可以傳入兔子或烏龜的實例 public LetOneStop(Animal an) { this.an = an; } //讓動物的線程停止 @Override public void win() { // 線程停止 an.stop(); } }
(五)創建一個主方法調用類,
package com.xykj.rabbit_tortoise; public class MainClass { /** * 龜兔賽跑:20米 * */ public static void main(String[] args) { //實例化烏龜和兔子 Tortoise tortoise = new Tortoise(); Rabbit rabbit = new Rabbit(); //回調方法的使用,誰先調用calltoback方法,另一個就不跑了 LetOneStop letOneStop1 = new LetOneStop(tortoise); rabbit.calltoback = letOneStop1;//讓兔子的回調方法里面存在烏龜對象的值,可以把烏龜stop LetOneStop letOneStop2 = new LetOneStop(rabbit); tortoise.calltoback = letOneStop2;//讓烏龜的回調方法里面存在兔子對象的值,可以把兔子stop //開始跑 tortoise.start(); rabbit.start(); } }
運行結果:
可以看到結果兔子贏了。
一般來說兔子獲得了勝利是在最后輸出的,
但是,由于線程一直在執行所以會出現:
“兔子跑了0.5米,距離終點還有0米”還沒來得及輸出完,
而“兔子獲得了勝利”已經輸出完畢了。
五.實例四:
在一個KFC內,服務員負責生產食物,消費者負責消費食物;
當生產到一定數量可以休息一下,直到消費完食物,再馬上生產,一直循環
程序涉及到的內容:
1.這設計到java模式思想:生產者消費者模式
2.要保證操作對象的統一性,即消費者和服務者都是跟同一個KFC發生關系的,KFC只能new一次
3.this.notifyAll();和this.wait();一個是所有喚醒的意思,一個是讓自己等待的意思;
比如本題中,生產者生產完畢后,先所有喚醒(包括消費者和生產者),再讓所有自己(生產者)等待
這時,消費者開始消費,直到食材不夠,先所有喚醒(包括消費者和生產者),再讓所有自己(消費者)等待
一直執行上面的操作的循環
4.生產者和消費者都要繼承Thread,才能實現多線程的啟動
程序設計的步驟思路:
1.創建一個食物類Food,有存放/獲取食物的名稱的方法
2.創建一個KFC類,有生產食物和消費食物的方法
3.創建一個客戶類Customer,繼承Thread,重寫run方法,在run方法里面進行消費食物操作
4.創建一個服務員類Waiter,繼承Thread,重寫run方法,在run方法里面進行生產食物的操作
5.創建主方法的調用類
(一)創建一個食物類Food
package com.xykj.producer_consumer; public class Food { String name=""; //通過構造方法傳入食物的名字 public Food(String name) { this.name=name; } //get、set 方法 public String getName() { return name; } public void setName(String name) { this.name = name; } }
(二)創建一個KFC類
package com.xykj.producer_consumer; import java.util.ArrayList; import java.util.List; public class KFC { //食物的種類 String[] names = { "薯條", "燒板", "雞翅", "可樂" }; //生產的最大值,到達后可以休息 static final int Max = 20; //存放食物的集合 List<Food> foods = new ArrayList<Food>(); // 生產食物的方法 public void prod(int index) { synchronized (this) { // 如果食物數量大于20 while (foods.size() > Max) { System.out.println("食材夠了"); this.notifyAll();//這個喚醒是針對生產者和消費者,有all try { String name=Thread.currentThread().getName(); this.wait();//這個喚醒是針對生產者,沒有all System.out.println("生產者:"+name); } catch (InterruptedException e) { e.printStackTrace(); } } // 開始生產食物食物//有一點要注意的 System.out.println("開始生產食物"); for (int i = 0; i < index; i++) { Food food = new Food(names[(int) (Math.random() * 4)]); foods.add(food); System.out.println("生產了" + food.getName() + foods.size()); } } } // 消費食物的方法 public void consu(int index) { synchronized (this) { while (foods.size() < index) { System.out.println("食材不夠了"); this.notifyAll();//這個喚醒是針對生產者和消費者,有all try { String name=Thread.currentThread().getName(); this.wait();//這個喚醒是針對消費者,沒有all System.out.println("消費者:"+name); } catch (InterruptedException e) { e.printStackTrace(); } } // 足夠消費 System.out.println("開始消費"); for (int i = 0; i < index; i++) { Food food = foods.remove(foods.size() - 1); System.out.println("消費了一個" + food.getName() + foods.size()); } } } }
(三)創建一個客戶類Customer
package com.xykj.producer_consumer; public class Customers extends Thread{ KFC kfc; //KFC要傳入,保證每一個服務員和用戶在同一個KFC對象內 public Customers(KFC kfc) { this.kfc=kfc; } @Override public void run() { int size=(int)(Math.random()*5);//每次要消費的食物的數量 while (true) { kfc.consu(size);//在消費的方法里面傳入參數 } } }
(四)創建一個服務員類Waiter
package com.xykj.producer_consumer; public class Waiter extends Thread{ KFC kfc; //KFC要傳入,保證每一個服務員和用戶在同一個KFC對象內 public Waiter(KFC kfc) { this.kfc=kfc; } @Override public void run() { int size=(int)(Math.random()*5)+5;//每次生產的數量 while (true) { kfc.prod(size);//傳入每次生產的數量 } } }
(五)創建主方法的調用類
package com.xykj.producer_consumer; public class MainClass { /** * 生產者消費者模式 * * */ public static void main(String[] args) { // 只實例化一個KFC對象,保證每一個服務員和用戶在同一個KFC對象內 KFC kfc = new KFC(); //實例化4個客戶對象 Customers c1 = new Customers(kfc); Customers c2 = new Customers(kfc); Customers c3 = new Customers(kfc); Customers c4 = new Customers(kfc); //實例化3個服務員對象 Waiter waiter1 = new Waiter(kfc); Waiter waiter2 = new Waiter(kfc); Waiter waiter3 = new Waiter(kfc); //讓所有的對象的線程都開始工作 waiter1.start(); waiter2.start(); waiter3.start(); c1.start(); c2.start(); c3.start(); c4.start(); } }
六.示例五:設計四個線程對象對同一個數據進行操作
兩個線程執行減操作,兩個線程執行加操作。
程序分析:
1.創建一個ThreadAddSub類繼承Thread,重寫run方法
2.在run方法里面實現加和減的操作,每次操作后睡眠1秒
3.創建主方法調用類
(一)創建一個ThreadAddSub類
package com.xykj.add; public class ThreadAddSub extends Thread { //判斷要進行的操作 boolean operate = true; //要操作的數 static int sum = 0; // 把操作運算通過構造方法傳進來 public ThreadAddSub(boolean operate) { super(); this.operate = operate; } @Override public void run() { super.run(); while (true) { if (operate) { sum+=5; System.out.println("加后,sum="+sum); } else { sum-=4; System.out.println("減后,sum="+sum); } try { sleep(500);// 睡眠0.5秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
(二)創建主方法調用類
emptypackage com.xykj.add; public class MainClass { /** * (線程同步) * */ public static void main(String[] args) { //創建一個存放ThreadAddSub對象的數組 ThreadAddSub[] tSub=new ThreadAddSub[4]; for (int i = 0; i < tSub.length; i++) { //把實例化ThreadAddSub對象賦值到數組內 //第一三個是true,二四個是false tSub[i]=new ThreadAddSub(i%2==0?true:false); //讓線程開始工作 tSub[i].start(); } } }
線程示例總結:
代碼塊鎖是一個防止數據發生錯誤的一個重要手段。
對象的統一性是非常重要的,這要想到對象的傳入問題,
要操作的對象只能new一次,其他的操作都是對這個傳入的對象進行的,
才能保證數據一致性,完整性和正確性。
練習題目:
1.(多線程)代碼實現火車站4個賣票窗口同時買票的場景,輸出示例:
窗口1賣票
窗口2賣票
窗口1賣票
...
2.(線程同步)代碼實現火車站4個窗口同時賣100張票的代碼邏輯,同一個窗口不能賣同一
張張票。
3.(線程通信)小明打算去提款機上取錢,發現卡上沒錢,這時候他告知媽媽去存錢,媽媽
存了錢了,告知小明存好了可以取錢了。(PS:小明分多次取錢,每次取100,當發現錢不夠
100,就等待媽媽存錢,小明他媽每次存2000,當發現錢小于100就存錢,就存錢,并且
通知小明去取錢,當大于100就等待小明錢不夠是再存)
4.(線程同步)設計四個線程對象對同一個數據進行操作,兩個線程執行減操作,兩個線程執行
加操作。
5.(線程通信)制作兩個線程對象,要求用同步塊的方式使第一個線程運行2次,然后將自己
阻塞起來,喚醒第二個線程,第二個線程再運行2次,然后將自己阻塞起來,喚醒第一個線
程……兩個線程交替執行。
6.(線程同步)設計4個線程,其中兩個線程每次對j增加1,另外兩個線程對j每次減少1。
7.(線程通信)子線程循環10次,接著主線程循環100,接著又回到子線程循環10次,接著
再回到主線程又循環100,如此循環50次。
關于“java多線程編程的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。