java多線程的常見例子


一.相關知識:

 

Java多線程程序設計到的知識:

(一)對同一個數量進行操作

(二)對同一個對象進行操作

(三)回調方法使用

(四)線程同步,死鎖問題

(五)線程通信

 等等

 

 

二.示例一:三個售票窗口同時出售20張票;

 

程序分析:1.票數要使用同一個靜態值

 2.為保證不會出現賣出同一個票數,要java多線程同步鎖。

設計思路:1.創建一個站台類Station,繼承Thread,重寫run方法,在run方法里面執行售票操作!售票要使用同步鎖:即有一個站台賣這張票時,其他站台要等這張票賣完!

2.創建主方法調用類

 

(一)創建一個站台類,繼承Thread

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.threadStation;  
  2.    
  3. public class Station extends Thread {  
  4.    
  5.         // 通過構造方法給線程名字賦值  
  6.         public Station(String name) {  
  7.              super(name);// 給線程名字賦值  
  8.         }  
  9.            
  10.         // 為了保持票數的一致,票數要靜態  
  11.         static int tick = 20;  
  12.            
  13.         // 創建一個靜態鑰匙  
  14.         static Object ob = "aa";//值是任意的  
  15.            
  16.         // 重寫run方法,實現買票操作  
  17.         @Override  
  18.         public void run() {  
  19.             while (tick > 0) {  
  20.                 synchronized (ob) {// 這個很重要,必須使用一個鎖,  
  21.                     // 進去的人會把鑰匙拿在手上,出來后才把鑰匙拿讓出來  
  22.                     if (tick > 0) {  
  23.                         System.out.println(getName() + "賣出了第" + tick + "張票");  
  24.                         tick--;  
  25.                     } else {  
  26.                         System.out.println("票賣完了");  
  27.                     }  
  28.                 }  
  29.                 try {  
  30.                      sleep(1000);//休息一秒  
  31.                 } catch (InterruptedException e) {  
  32.                     e.printStackTrace();  
  33.                 }  
  34.                
  35.             }  
  36.     }  
  37.    
  38. }  
  39.    
  40.    

 

 

 

(二)創建主方法調用類

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.threadStation;  
  2.    
  3. public class MainClass {  
  4.     /**  
  5.      * java多線程同步鎖的使用  
  6.      * 示例:三個售票窗口同時出售10張票  
  7.      * */  
  8.     public static void main(String[] args) {  
  9.         //實例化站台對象,並為每一個站台取名字  
  10.          Station station1=new Station("窗口1");  
  11.          Station station2=new Station("窗口2");  
  12.          Station station3=new Station("窗口3");  
  13.        
  14.         // 讓每一個站台對象各自開始工作  
  15.          station1.start();  
  16.          station2.start();  
  17.          station3.start();  
  18.        
  19.     }  
  20.    
  21. }  

 

 

 

程序運行結果:

 

 

 

 

 

可以看到票數是不會有錯的!

 

 

 

 

三.示例二:兩個人AB通過一個賬戶A在櫃台取錢和B在ATM機取錢!

 

程序分析:錢的數量要設置成一個靜態的變量。兩個人要取的同一個對象值

 

(一)創建一個Bank類

 

 

[plain]  view plain  copy
 
 print?
  1.    
  2. package com.xykj.bank;  
  3.    
  4. public class Bank {  
  5.    
  6.     // 假設一個賬戶有1000塊錢  
  7.     static int money = 1000;  
  8.        
  9.     // 櫃台Counter取錢的方法  
  10.     public void Counter(int money) {// 參數是每次取走的錢  
  11.         Bank.money -= money;//取錢后總數減少  
  12.         System.out.println("A取走了" + money + "還剩下" + (Bank.money));  
  13.     }  
  14.        
  15.     // ATM取錢的方法  
  16.     public void ATM(int money) {// 參數是每次取走的錢  
  17.         Bank.money -= money;//取錢后總數減少  
  18.         System.out.println("B取走了" + money + "還剩下" + (Bank.money));  
  19.     }  
  20.        
  21. }  

 

 

(二)創建一個PersonA類

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.bank;  
  2.    
  3. public class PersonA extends Thread {  
  4.     // 創建銀行對象  
  5.     Bank bank;  
  6.        
  7.     // 通過構造器傳入銀行對象,確保兩個人進入的是一個銀行  
  8.     public PersonA(Bank bank) {  
  9.          this.bank = bank;  
  10.     }  
  11.       
  12.     //重寫run方法,在里面實現使用櫃台取錢  
  13.     @Override  
  14.         public void run() {  
  15.             while (Bank.money >= 100) {  
  16.                 bank.Counter(100);// 每次取100塊  
  17.             try {  
  18.                 sleep(100);// 取完休息0.1秒  
  19.             } catch (InterruptedException e) {  
  20.                 e.printStackTrace();  
  21.             }  
  22.         }  
  23.     }  
  24. }  

 

 

 

(三)創建一個PersonB類

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.bank;  
  2.    
  3. public class PersonB extends Thread {  
  4.     // 創建銀行對象  
  5.     Bank bank;  
  6.        
  7.     // 通過構造器傳入銀行對象,確保兩個人進入的是一個銀行  
  8.     public PersonB(Bank bank) {  
  9.         this.bank = bank;  
  10.     }  
  11.        
  12.     // 重寫run方法,在里面實現使用櫃台取錢  
  13.     @Override  
  14.     public void run() {  
  15.         while (Bank.money >= 200) {  
  16.             bank.ATM(200);// 每次取200塊  
  17.             try {  
  18.                 sleep(100);// 取完休息0.1秒  
  19.             } catch (InterruptedException e) {  
  20.                 e.printStackTrace();  
  21.             }  
  22.         }  
  23.            
  24.     }  
  25. }  
  26.    

 

 

(四)創建主方法的調用類

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.bank;  
  2.    
  3. public class MainClass {  
  4.     /**  
  5.      * 兩個人AB通過一個賬戶A在櫃台取錢和B在ATM機取錢  
  6.      * */  
  7.     public static void main(String[] args) {  
  8.         // 實力化一個銀行對象  
  9.         Bank bank = new Bank();  
  10.         // 實例化兩個人,傳入同一個銀行的對象  
  11.         PersonA pA = new PersonA(bank);  
  12.         PersonB pB = new PersonB(bank);  
  13.         // 兩個人開始取錢  
  14.         pA.start();  
  15.         pB.start();  
  16.            
  17.     }  
  18.    
  19. }  

 

 

  

運行結果:

 

 

 

可以看到取完就停止運行了。

 

 

 

四.示例三:龜兔賽跑問題

 

龜兔賽跑: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動物類

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.rabbit_tortoise;  
  2.    
  3. public abstract class Animal extends Thread{  
  4.   
  5.     public double length=20;//比賽的長度  
  6.       
  7.     public abstract void runing();//抽象方法需要子類實現  
  8.       
  9.     //在父類重寫run方法,在子類只要重寫running方法就可以了  
  10.     @Override  
  11.     public void run() {  
  12.         super.run();  
  13.         while (length>0) {  
  14.              runing();  
  15.         }  
  16.     }  
  17.       
  18.     //在需要回調數據的地方(兩個子類需要),聲明一個接口  
  19.     public static interface Calltoback{  
  20.         public void win();  
  21.     }  
  22.       
  23.     //2.創建接口對象  
  24.     public Calltoback calltoback;  
  25.       
  26. }  

 

 

(二)創建Rabbit兔子類

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.rabbit_tortoise;  
  2.    
  3. public class Rabbit extends Animal {  
  4.        
  5.     public Rabbit() {  
  6.         setName("兔子");// Thread的方法,給線程賦值名字  
  7.     }  
  8.        
  9.     // 重寫running方法,編寫兔子的奔跑操作  
  10.     @Override  
  11.     public void runing() {  
  12.         // 跑的距離  
  13.         double dis = 0.5;  
  14.         length -= dis;//跑完后距離減少  
  15.         if (length <= 0) {  
  16.             length = 0;  
  17.             System.out.println("兔子獲得了勝利");  
  18.             //給回調對象賦值,讓烏龜不要再跑了  
  19.             if (calltoback != null) {  
  20.                 calltoback.win();  
  21.             }  
  22.         }  
  23.         System.out.println("兔子跑了" + dis + "米,距離終點還有" + (int)length + "米");  
  24.            
  25.         if (length % 2 == 0) {// 兩米休息一次  
  26.             try {  
  27.                 sleep(1000);  
  28.             } catch (InterruptedException e) {  
  29.                 e.printStackTrace();  
  30.             }  
  31.         }  
  32.     }  
  33. }  
  34.    

 

 

 

(三)創建Tortoise烏龜類

 

 

[plain]  view plain  copy
 
 print?
  1.    
  2. package com.xykj.rabbit_tortoise;  
  3.    
  4. public class Tortoise extends Animal {  
  5.        
  6.     public Tortoise() {  
  7.         setName("烏龜");// Thread的方法,給線程賦值名字  
  8.     }  
  9.        
  10.     // 重寫running方法,編寫烏龜的奔跑操作  
  11.     @Override  
  12.     public void runing() {  
  13.         // 跑的距離  
  14.         double dis = 0.1;  
  15.         length -= dis;  
  16.         if (length <= 0) {  
  17.             length = 0;  
  18.             System.out.println("烏龜獲得了勝利");  
  19.             // 讓兔子不要在跑了  
  20.             if (calltoback != null) {  
  21.                 calltoback.win();  
  22.             }  
  23.         }  
  24.         System.out.println("烏龜跑了" + dis + "米,距離終點還有" + (int) length + "米");  
  25.         try {  
  26.             sleep(100);  
  27.         } catch (InterruptedException e) {  
  28.             e.printStackTrace();  
  29.         }  
  30.     }  
  31. }  

 

 

 

 

(四)創建一個讓動物線程停止的類,這里要實現回調接口

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.rabbit_tortoise;  
  2.    
  3. import com.xykj.rabbit_tortoise.Animal.Calltoback;  
  4.    
  5. public class LetOneStop implements Calltoback {  
  6.   
  7.     // 動物對象  
  8.     Animal an;  
  9.       
  10.     // 獲取動物對象,可以傳入兔子或烏龜的實例  
  11.     public LetOneStop(Animal an) {  
  12.         this.an = an;  
  13.     }  
  14.       
  15.     //讓動物的線程停止  
  16.     @Override  
  17.     public void win() {  
  18.         // 線程停止  
  19.         an.stop();  
  20.     }  
  21.        
  22. }  
  23.    
  24.    
  25.    

 

 

 

 

(五)創建一個主方法調用類,

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.rabbit_tortoise;  
  2.    
  3. public class MainClass {  
  4.     /**  
  5.      * 龜兔賽跑:20米        
  6.      * */  
  7.     public static void main(String[] args) {  
  8.         //實例化烏龜和兔子  
  9.         Tortoise tortoise = new Tortoise();  
  10.         Rabbit rabbit = new Rabbit();  
  11.         //回調方法的使用,誰先調用calltoback方法,另一個就不跑了  
  12.         LetOneStop letOneStop1 = new LetOneStop(tortoise);  
  13.         rabbit.calltoback = letOneStop1;//讓兔子的回調方法里面存在烏龜對象的值,可以把烏龜stop  
  14.         LetOneStop letOneStop2 = new LetOneStop(rabbit);  
  15.         tortoise.calltoback = letOneStop2;//讓烏龜的回調方法里面存在兔子對象的值,可以把兔子stop  
  16.         //開始跑  
  17.         tortoise.start();  
  18.         rabbit.start();  
  19.        
  20.     }  
  21.    
  22. }  

 

 

 

運行結果:

 

 

 

可以看到結果兔子贏了。

一般來說兔子獲得了勝利是在最后輸出的,

但是,由於線程一直在執行所以會出現:

“兔子跑了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

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.producer_consumer;  
  2.    
  3. public class Food {  
  4.     String name="";  
  5.     //通過構造方法傳入食物的名字  
  6.     public Food(String name) {  
  7.         this.name=name;  
  8.     }  
  9.     //get、set 方法  
  10.     public String getName() {  
  11.         return name;  
  12.     }  
  13.     public void setName(String name) {  
  14.         this.name = name;  
  15.     }  
  16. }  

 

 

 

(二)創建一個KFC類

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.producer_consumer;  
  2. import java.util.ArrayList;  
  3. import java.util.List;  
  4.    
  5. public class KFC {  
  6.   
  7.     //食物的種類  
  8.     String[] names = { "薯條", "燒板", "雞翅", "可樂" };  
  9.       
  10.     //生產的最大值,到達后可以休息  
  11.     static final int Max = 20;  
  12.       
  13.     //存放食物的集合  
  14.     List<food> foods = new ArrayList<food>();  
  15.        
  16.     // 生產食物的方法  
  17.     public void prod(int index) {  
  18.         synchronized (this) {  
  19.             // 如果食物數量大於20  
  20.             while (foods.size() > Max) {  
  21.                 System.out.println("食材夠了");  
  22.                 this.notifyAll();//這個喚醒是針對生產者和消費者,有all  
  23.                 try {  
  24.                     String name=Thread.currentThread().getName();  
  25.                     this.wait();//這個喚醒是針對生產者,沒有all  
  26.                     System.out.println("生產者:"+name);  
  27.                 } catch (InterruptedException e) {  
  28.                     e.printStackTrace();  
  29.                 }  
  30.             }  
  31.                
  32.             // 開始生產食物食物//有一點要注意的  
  33.             System.out.println("開始生產食物");  
  34.             for (int i = 0; i < index; i++) {  
  35.                 Food food = new Food(names[(int) (Math.random() * 4)]);  
  36.                 foods.add(food);  
  37.                 System.out.println("生產了" + food.getName() + foods.size());  
  38.             }  
  39.         }  
  40.     }  
  41.        
  42.     // 消費食物的方法  
  43.     public void consu(int index) {   
  44.         synchronized (this) {  
  45.             while (foods.size() < index) {  
  46.                 System.out.println("食材不夠了");  
  47.                 this.notifyAll();//這個喚醒是針對生產者和消費者,有all  
  48.                 try {  
  49.                     String name=Thread.currentThread().getName();  
  50.                     this.wait();//這個喚醒是針對消費者,沒有all  
  51.                     System.out.println("消費者:"+name);  
  52.                 } catch (InterruptedException e) {  
  53.                     e.printStackTrace();  
  54.                 }  
  55.             }  
  56.             // 足夠消費  
  57.             System.out.println("開始消費");  
  58.             for (int i = 0; i < index; i++) {  
  59.                 Food food = foods.remove(foods.size() - 1);  
  60.                 System.out.println("消費了一個" + food.getName() + foods.size());  
  61.             }  
  62.         }  
  63.     }  
  64. }  
  65.  </food></food>  

 

 

 

(三)創建一個客戶類Customer

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.producer_consumer;  
  2.    
  3. public class Customers extends Thread{  
  4.     KFC kfc;  
  5.     //KFC要傳入,保證每一個服務員和用戶在同一個KFC對象內  
  6.     public Customers(KFC kfc) {  
  7.         this.kfc=kfc;  
  8.     }  
  9.     @Override  
  10.     public void run() {  
  11.         int size=(int)(Math.random()*5);//每次要消費的食物的數量  
  12.         while (true) {  
  13.             kfc.consu(size);//在消費的方法里面傳入參數  
  14.         }  
  15.        
  16.     }  
  17. }  
  18.    

 

 

 

 

(四)創建一個服務員類Waiter

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.producer_consumer;  
  2.    
  3. public class Waiter extends Thread{  
  4.     KFC kfc;  
  5.     //KFC要傳入,保證每一個服務員和用戶在同一個KFC對象內  
  6.     public Waiter(KFC kfc) {  
  7.         this.kfc=kfc;  
  8.     }  
  9.     @Override  
  10.     public void run() {  
  11.         int size=(int)(Math.random()*5)+5;//每次生產的數量  
  12.         while (true) {  
  13.             kfc.prod(size);//傳入每次生產的數量  
  14.         }  
  15.        
  16.     }  
  17. }  

 

 

 

(五)創建主方法的調用類

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.producer_consumer;  
  2.    
  3. public class MainClass {  
  4.     /**  
  5.      * 生產者消費者模式  
  6.      *  
  7.      * */  
  8.     public static void main(String[] args) {  
  9.       
  10.         // 只實例化一個KFC對象,保證每一個服務員和用戶在同一個KFC對象內  
  11.         KFC kfc = new KFC();  
  12.           
  13.         //實例化4個客戶對象  
  14.         Customers c1 = new Customers(kfc);  
  15.         Customers c2 = new Customers(kfc);  
  16.         Customers c3 = new Customers(kfc);  
  17.         Customers c4 = new Customers(kfc);  
  18.           
  19.         //實例化3個服務員對象  
  20.         Waiter waiter1 = new Waiter(kfc);  
  21.         Waiter waiter2 = new Waiter(kfc);  
  22.         Waiter waiter3 = new Waiter(kfc);  
  23.           
  24.         //讓所有的對象的線程都開始工作  
  25.         waiter1.start();  
  26.         waiter2.start();  
  27.         waiter3.start();  
  28.         c1.start();  
  29.         c2.start();  
  30.         c3.start();  
  31.         c4.start();  
  32.     }  
  33.        
  34. }  
  35.    
  36.    

 

 

 

六.示例五:設計四個線程對象對同一個數據進行操作,

  兩個線程執行減操作,兩個線程執行加操作。

 

程序分析:1.創建一個ThreadAddSub類繼承Thread,重寫run方法

   2.在run方法里面實現加和減的操作,每次操作后睡眠1秒

   3.創建主方法調用類

 

(一)創建一個ThreadAddSub類

 

 

[plain]  view plain  copy
 
 print?
  1. package com.xykj.add;  
  2.    
  3. public class ThreadAddSub extends Thread {  
  4.     //判斷要進行的操作  
  5.     boolean operate = true;  
  6.     //要操作的數  
  7.     static int sum = 0;  
  8.        
  9.     // 把操作運算通過構造方法傳進來  
  10.     public ThreadAddSub(boolean operate) {  
  11.         super();  
  12.         this.operate = operate;  
  13.     }  
  14.        
  15.     @Override  
  16.     public void run() {  
  17.         super.run();  
  18.         while (true) {  
  19.             if (operate) {  
  20.                 sum+=5;  
  21.                 System.out.println("加后,sum="+sum);  
  22.             } else {  
  23.                 sum-=4;  
  24.                 System.out.println("減后,sum="+sum);  
  25.             }  
  26.             try {  
  27.                 sleep(500);// 睡眠0.5秒  
  28.             } catch (InterruptedException e) {  
  29.                 e.printStackTrace();  
  30.             }  
  31.         }  
  32.        
  33.     }  
  34. }  

 

 

 

 (二)創建主方法調用類

 

 

[plain]  view plain  copy
 
 print?
  1. emptypackage com.xykj.add;  
  2.    
  3. public class MainClass {  
  4.     /**  
  5.      * (線程同步)  
  6.      * */  
  7.     public static void main(String[] args) {  
  8.       
  9.         //創建一個存放ThreadAddSub對象的數組  
  10.         ThreadAddSub[] tSub=new ThreadAddSub[4];  
  11.         for (int i = 0; i < tSub.length; i++) {  
  12.           
  13.         //把實例化ThreadAddSub對象賦值到數組內  
  14.         //第一三個是true,二四個是false  
  15.         tSub[i]=new ThreadAddSub(i%2==0?true:false);  
  16.           
  17.         //讓線程開始工作  
  18.         tSub[i].start();  
  19.         }  
  20.        
  21.     }  
  22.    
  23. }  

 

 

  

 

線程示例總結:

代碼塊鎖是一個防止數據發生錯誤的一個重要手段。

對象的統一性是非常重要的,這要想到對象的傳入問題,

要操作的對象只能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次。

 

轉,地址:http://blog.csdn.net/wenzhi20102321/article/details/52524545


免責聲明!

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



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