1. 程式人生 > >java基礎總結(三十二)--java多執行緒程式設計例項

java基礎總結(三十二)--java多執行緒程式設計例項

來自:https://blog.csdn.net/qq_34996727/article/details/80416277或者https://www.cnblogs.com/pureEve/p/6524366.html

一.相關知識:

 

Java多執行緒程式設計到的知識:

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

(二)對同一個物件進行操作

(三)回撥方法使用

(四)執行緒同步,死鎖問題

(五)執行緒通訊

 等等

 

 

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

 

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

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

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

2.建立主方法呼叫類

 

(一)建立一個站臺類,繼承Thread

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

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

}

  

}

}

  

}

  

 

 

 

 

 

 

(二)建立主方法呼叫類

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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

  

}

  

}

  

 

 

 

 

 

程式執行結果:

 

 

 

 

 

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

 

 

 

 

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

 

程式分析:錢的數量要設定成一個靜態的變數。兩個人要取的同一個物件值

 

(一)建立一個Bank類

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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類

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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類

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

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

}

}

  

}

}

  

 

 

 

 

(四)建立主方法的呼叫類

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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動物類

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

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兔子類

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

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烏龜類

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

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

}

}

}

  

 

 

 

 

 

(四)建立一個讓動物執行緒停止的類,這裡要實現回撥介面

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

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

}

  

}

  

 

 

 

 

 

 

(五)建立一個主方法呼叫類,

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

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

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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類

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

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

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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);//傳入每次生產的數量

}

  

}

}

  

 

 

 

 

 

(五)建立主方法的呼叫類

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

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類

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

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

}

}

  

}

}

  

 

 

 

 

 

 (二)建立主方法呼叫類

 

 

 

 

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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次。