java多線程編程實例
一.相關(guān)知識:
Java多線程程序設(shè)計到的知識:
(一)對同一個數(shù)量進(jìn)行操作
(二)對同一個對象進(jìn)行操作
(三)回調(diào)方法使用
(四)線程同步,死鎖問題
(五)線程通信
等等
二.示例一:三個售票窗口同時出售20張票;
程序分析:
1.票數(shù)要使用同一個靜態(tài)值
2.為保證不會出現(xiàn)賣出同一個票數(shù),要java多線程同步鎖。
設(shè)計思路:
1.創(chuàng)建一個站臺類Station,繼承Thread,重寫run方法,在run方法里面執(zhí)行售票操作!售票要使用同步鎖:即有一個站臺賣這張票時,其他站臺要等這張票賣完!
2.創(chuàng)建主方法調(diào)用類
(一)創(chuàng)建一個站臺類,繼承Thread
package com.xykj.threadStation;
public class Station extends Thread {
// 通過構(gòu)造方法給線程名字賦值
public Station(String name) {
super(name);// 給線程名字賦值
}
// 為了保持票數(shù)的一致,票數(shù)要靜態(tài)
static int tick = 20;
// 創(chuàng)建一個靜態(tài)鑰匙
static Object ob = "aa";//值是任意的
// 重寫run方法,實現(xiàn)買票操作
@Override
public void run() {
while (tick > 0) {
synchronized (ob) {// 這個很重要,必須使用一個鎖,
// 進(jìn)去的人會把鑰匙拿在手上,出來后才把鑰匙拿讓出來
if (tick > 0) {
System.out.println(getName() + "賣出了第" + tick + "張票");
tick--;
} else {
System.out.println("票賣完了");
}
}
try {
sleep(1000);//休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(二)創(chuàng)建主方法調(diào)用類
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();
}
}
程序運行結(jié)果:
窗口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張票 票賣完了
可以看到票數(shù)是不會有錯的!
三.示例二:兩個人AB通過一個賬戶A在柜臺取錢和B在ATM機取錢!
程序分析:錢的數(shù)量要設(shè)置成一個靜態(tài)的變量。兩個人要取的同一個對象值
(一)創(chuàng)建一個Bank類
package com.xykj.bank;
public class Bank {
// 假設(shè)一個賬戶有1000塊錢
static int money = 1000;
// 柜臺Counter取錢的方法
public void Counter(int money) {// 參數(shù)是每次取走的錢
Bank.money -= money;//取錢后總數(shù)減少
System.out.println("A取走了" + money + "還剩下" + (Bank.money));
}
// ATM取錢的方法
public void ATM(int money) {// 參數(shù)是每次取走的錢
Bank.money -= money;//取錢后總數(shù)減少
System.out.println("B取走了" + money + "還剩下" + (Bank.money));
}
}
(二)創(chuàng)建一個PersonA類
package com.xykj.bank;
public class PersonA extends Thread {
// 創(chuàng)建銀行對象
Bank bank;
// 通過構(gòu)造器傳入銀行對象,確保兩個人進(jìn)入的是一個銀行
public PersonA(Bank bank) {
this.bank = bank;
}
//重寫run方法,在里面實現(xiàn)使用柜臺取錢
@Override
public void run() {
while (Bank.money >= 100) {
bank.Counter(100);// 每次取100塊
try {
sleep(100);// 取完休息0.1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(三)創(chuàng)建一個PersonB類
package com.xykj.bank;
public class PersonB extends Thread {
// 創(chuàng)建銀行對象
Bank bank;
// 通過構(gòu)造器傳入銀行對象,確保兩個人進(jìn)入的是一個銀行
public PersonB(Bank bank) {
this.bank = bank;
}
// 重寫run方法,在里面實現(xiàn)使用柜臺取錢
@Override
public void run() {
while (Bank.money >= 200) {
bank.ATM(200);// 每次取200塊
try {
sleep(100);// 取完休息0.1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(四)創(chuàng)建主方法的調(diào)用類
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();
}
}
運行結(jié)果:

可以看到取完就停止運行了。
四.示例三:龜兔賽跑問題
龜兔賽跑:20米//只要為了看到效果,所有距離縮短了
要求:
1.兔子每秒0.5米的速度,每跑2米休息10秒,
2.烏龜每秒跑0.1米,不休息
3.其中一個跑到終點后另一個不跑了!
程序設(shè)計思路:
1.創(chuàng)建一個Animal動物類,繼承Thread,編寫一個running抽象方法,重寫run方法,把running方法在run方法里面調(diào)用。
2.創(chuàng)建Rabbit兔子類和Tortoise烏龜類,繼承動物類
3.兩個子類重寫running方法
4.本題的第3個要求涉及到線程回調(diào)。需要在動物類創(chuàng)建一個回調(diào)接口,創(chuàng)建一個回調(diào)對象
(一)創(chuàng)建Animal動物類
package com.xykj.rabbit_tortoise;
public abstract class Animal extends Thread{
public double length=20;//比賽的長度
public abstract void runing();//抽象方法需要子類實現(xiàn)
//在父類重寫run方法,在子類只要重寫running方法就可以了
@Override
public void run() {
super.run();
while (length>0) {
runing();
}
}
//在需要回調(diào)數(shù)據(jù)的地方(兩個子類需要),聲明一個接口
public static interface Calltoback{
public void win();
}
//2.創(chuàng)建接口對象
public Calltoback calltoback;
}
(二)創(chuàng)建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("兔子獲得了勝利");
//給回調(diào)對象賦值,讓烏龜不要再跑了
if (calltoback != null) {
calltoback.win();
}
}
System.out.println("兔子跑了" + dis + "米,距離終點還有" + (int)length + "米");
if (length % 2 == 0) {// 兩米休息一次
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(三)創(chuàng)建Tortoise烏龜類
package com.xykj.rabbit_tortoise;
public class Tortoise extends Animal {
public Tortoise() {
setName("烏龜");// Thread的方法,給線程賦值名字
}
// 重寫running方法,編寫烏龜?shù)谋寂懿僮?
@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();
}
}
}
(四)創(chuàng)建一個讓動物線程停止的類,這里要實現(xiàn)回調(diào)接口
package com.xykj.rabbit_tortoise;
import com.xykj.rabbit_tortoise.Animal.Calltoback;
public class LetOneStop implements Calltoback {
// 動物對象
Animal an;
// 獲取動物對象,可以傳入兔子或烏龜?shù)膶嵗?
public LetOneStop(Animal an) {
this.an = an;
}
//讓動物的線程停止
@Override
public void win() {
// 線程停止
an.stop();
}
}
(五)創(chuàng)建一個主方法調(diào)用類,
package com.xykj.rabbit_tortoise;
public class MainClass {
/**
* 龜兔賽跑:20米
* */
public static void main(String[] args) {
//實例化烏龜和兔子
Tortoise tortoise = new Tortoise();
Rabbit rabbit = new Rabbit();
//回調(diào)方法的使用,誰先調(diào)用calltoback方法,另一個就不跑了
LetOneStop letOneStop1 = new LetOneStop(tortoise);
rabbit.calltoback = letOneStop1;//讓兔子的回調(diào)方法里面存在烏龜對象的值,可以把烏龜stop
LetOneStop letOneStop2 = new LetOneStop(rabbit);
tortoise.calltoback = letOneStop2;//讓烏龜?shù)幕卣{(diào)方法里面存在兔子對象的值,可以把兔子stop
//開始跑
tortoise.start();
rabbit.start();
}
}
運行結(jié)果:

可以看到結(jié)果兔子贏了。
一般來說兔子獲得了勝利是在最后輸出的,
但是,由于線程一直在執(zhí)行所以會出現(xiàn):
“兔子跑了0.5米,距離終點還有0米”還沒來得及輸出完,
而“兔子獲得了勝利”已經(jīng)輸出完畢了。
五.實例四:
在一個KFC內(nèi),服務(wù)員負(fù)責(zé)生產(chǎn)食物,消費者負(fù)責(zé)消費食物;
當(dāng)生產(chǎn)到一定數(shù)量可以休息一下,直到消費完食物,再馬上生產(chǎn),一直循環(huán)
程序涉及到的內(nèi)容:
1.這設(shè)計到j(luò)ava模式思想:生產(chǎn)者消費者模式
2.要保證操作對象的統(tǒng)一性,即消費者和服務(wù)者都是跟同一個KFC發(fā)生關(guān)系的,KFC只能new一次
3.this.notifyAll();和this.wait();一個是所有喚醒的意思,一個是讓自己等待的意思;
比如本題中,生產(chǎn)者生產(chǎn)完畢后,先所有喚醒(包括消費者和生產(chǎn)者),再讓所有自己(生產(chǎn)者)等待
這時,消費者開始消費,直到食材不夠,先所有喚醒(包括消費者和生產(chǎn)者),再讓所有自己(消費者)等待
一直執(zhí)行上面的操作的循環(huán)
4.生產(chǎn)者和消費者都要繼承Thread,才能實現(xiàn)多線程的啟動
程序設(shè)計的步驟思路:
1.創(chuàng)建一個食物類Food,有存放/獲取食物的名稱的方法
2.創(chuàng)建一個KFC類,有生產(chǎn)食物和消費食物的方法
3.創(chuàng)建一個客戶類Customer,繼承Thread,重寫run方法,在run方法里面進(jìn)行消費食物操作
4.創(chuàng)建一個服務(wù)員類Waiter,繼承Thread,重寫run方法,在run方法里面進(jìn)行生產(chǎn)食物的操作
5.創(chuàng)建主方法的調(diào)用類
(一)創(chuàng)建一個食物類Food
package com.xykj.producer_consumer;
public class Food {
String name="";
//通過構(gòu)造方法傳入食物的名字
public Food(String name) {
this.name=name;
}
//get、set 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
(二)創(chuàng)建一個KFC類
package com.xykj.producer_consumer;
import java.util.ArrayList;
import java.util.List;
public class KFC {
//食物的種類
String[] names = { "薯條", "燒板", "雞翅", "可樂" };
//生產(chǎn)的最大值,到達(dá)后可以休息
static final int Max = 20;
//存放食物的集合
List<Food> foods = new ArrayList<Food>();
// 生產(chǎn)食物的方法
public void prod(int index) {
synchronized (this) {
// 如果食物數(shù)量大于20
while (foods.size() > Max) {
System.out.println("食材夠了");
this.notifyAll();//這個喚醒是針對生產(chǎn)者和消費者,有all
try {
String name=Thread.currentThread().getName();
this.wait();//這個喚醒是針對生產(chǎn)者,沒有all
System.out.println("生產(chǎn)者:"+name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 開始生產(chǎn)食物食物//有一點要注意的
System.out.println("開始生產(chǎn)食物");
for (int i = 0; i < index; i++) {
Food food = new Food(names[(int) (Math.random() * 4)]);
foods.add(food);
System.out.println("生產(chǎn)了" + food.getName() + foods.size());
}
}
}
// 消費食物的方法
public void consu(int index) {
synchronized (this) {
while (foods.size() < index) {
System.out.println("食材不夠了");
this.notifyAll();//這個喚醒是針對生產(chǎn)者和消費者,有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());
}
}
}
}
(三)創(chuàng)建一個客戶類Customer
package com.xykj.producer_consumer;
public class Customers extends Thread{
KFC kfc;
//KFC要傳入,保證每一個服務(wù)員和用戶在同一個KFC對象內(nèi)
public Customers(KFC kfc) {
this.kfc=kfc;
}
@Override
public void run() {
int size=(int)(Math.random()*5);//每次要消費的食物的數(shù)量
while (true) {
kfc.consu(size);//在消費的方法里面?zhèn)魅雲(yún)?shù)
}
}
}
(四)創(chuàng)建一個服務(wù)員類Waiter
package com.xykj.producer_consumer;
public class Waiter extends Thread{
KFC kfc;
//KFC要傳入,保證每一個服務(wù)員和用戶在同一個KFC對象內(nèi)
public Waiter(KFC kfc) {
this.kfc=kfc;
}
@Override
public void run() {
int size=(int)(Math.random()*5)+5;//每次生產(chǎn)的數(shù)量
while (true) {
kfc.prod(size);//傳入每次生產(chǎn)的數(shù)量
}
}
}
(五)創(chuàng)建主方法的調(diào)用類
package com.xykj.producer_consumer;
public class MainClass {
/**
* 生產(chǎn)者消費者模式
*
* */
public static void main(String[] args) {
// 只實例化一個KFC對象,保證每一個服務(wù)員和用戶在同一個KFC對象內(nèi)
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個服務(wù)員對象
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();
}
}
六.示例五:設(shè)計四個線程對象對同一個數(shù)據(jù)進(jìn)行操作
兩個線程執(zhí)行減操作,兩個線程執(zhí)行加操作。
程序分析:
1.創(chuàng)建一個ThreadAddSub類繼承Thread,重寫run方法
2.在run方法里面實現(xiàn)加和減的操作,每次操作后睡眠1秒
3.創(chuàng)建主方法調(diào)用類
(一)創(chuàng)建一個ThreadAddSub類
package com.xykj.add;
public class ThreadAddSub extends Thread {
//判斷要進(jìn)行的操作
boolean operate = true;
//要操作的數(shù)
static int sum = 0;
// 把操作運算通過構(gòu)造方法傳進(jìn)來
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();
}
}
}
}
(二)創(chuàng)建主方法調(diào)用類
emptypackage com.xykj.add;
public class MainClass {
/**
* (線程同步)
* */
public static void main(String[] args) {
//創(chuàng)建一個存放ThreadAddSub對象的數(shù)組
ThreadAddSub[] tSub=new ThreadAddSub[4];
for (int i = 0; i < tSub.length; i++) {
//把實例化ThreadAddSub對象賦值到數(shù)組內(nèi)
//第一三個是true,二四個是false
tSub[i]=new ThreadAddSub(i%2==0?true:false);
//讓線程開始工作
tSub[i].start();
}
}
}
線程示例總結(jié):
代碼塊鎖是一個防止數(shù)據(jù)發(fā)生錯誤的一個重要手段。
對象的統(tǒng)一性是非常重要的,這要想到對象的傳入問題,
要操作的對象只能new一次,其他的操作都是對這個傳入的對象進(jìn)行的,
才能保證數(shù)據(jù)一致性,完整性和正確性。
練習(xí)題目:
1.(多線程)代碼實現(xiàn)火車站4個賣票窗口同時買票的場景,輸出示例:
窗口1賣票
窗口2賣票
窗口1賣票
...
2.(線程同步)代碼實現(xiàn)火車站4個窗口同時賣100張票的代碼邏輯,同一個窗口不能賣同一
張張票。
3.(線程通信)小明打算去提款機上取錢,發(fā)現(xiàn)卡上沒錢,這時候他告知媽媽去存錢,媽媽
存了錢了,告知小明存好了可以取錢了。(PS:小明分多次取錢,每次取100,當(dāng)發(fā)現(xiàn)錢不夠
100,就等待媽媽存錢,小明他媽每次存2000,當(dāng)發(fā)現(xiàn)錢小于100就存錢,就存錢,并且
通知小明去取錢,當(dāng)大于100就等待小明錢不夠是再存)
4.(線程同步)設(shè)計四個線程對象對同一個數(shù)據(jù)進(jìn)行操作,兩個線程執(zhí)行減操作,兩個線程執(zhí)行
加操作。
5.(線程通信)制作兩個線程對象,要求用同步塊的方式使第一個線程運行2次,然后將自己
阻塞起來,喚醒第二個線程,第二個線程再運行2次,然后將自己阻塞起來,喚醒第一個線
程……兩個線程交替執(zhí)行。
6.(線程同步)設(shè)計4個線程,其中兩個線程每次對j增加1,另外兩個線程對j每次減少1。
7.(線程通信)子線程循環(huán)10次,接著主線程循環(huán)100,接著又回到子線程循環(huán)10次,接著
再回到主線程又循環(huán)100,如此循環(huán)50次。
總結(jié)
以上就是本文關(guān)于java多線程編程實例的全部內(nèi)容,希望對大家學(xué)習(xí)Java有所幫助,感興趣的朋友可以繼續(xù)參閱本站:淺談Java多線程的優(yōu)點及代碼示例、Java多線程之線程通信生產(chǎn)者消費者模式及等待喚醒機制代碼詳解、Java多線程編程實現(xiàn)socket通信示例代碼等,有什么問題可以隨時留言,小編會及時回復(fù)大家的。感謝朋友們對本站的支持!
相關(guān)文章
SpringBoot監(jiān)聽Redis key失效事件的實現(xiàn)代碼
這篇文章給大家介紹了SpringBoot實現(xiàn)監(jiān)聽Redis key失效事件的方法,文中通過代碼示例給大家講解的非常詳細(xì),具有一定的參考價值,需要的朋友可以參考下2024-02-02
使用Spring?Cloud?Stream處理Java消息流的操作流程
Spring?Cloud?Stream是一個用于構(gòu)建消息驅(qū)動微服務(wù)的框架,能夠與各種消息中間件集成,如RabbitMQ、Kafka等,今天我們來探討如何使用Spring?Cloud?Stream來處理Java消息流,需要的朋友可以參考下2024-08-08
SpringBoot項目War包部署無法注冊到Nacos中的解決
這篇文章主要介紹了SpringBoot項目War包部署無法注冊到Nacos中的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
Java分支結(jié)構(gòu)和循環(huán)結(jié)構(gòu)原理與用法詳解
這篇文章主要介紹了Java分支結(jié)構(gòu)和循環(huán)結(jié)構(gòu)原理與用法,結(jié)合實例形式分析了java分支結(jié)構(gòu)、循環(huán)結(jié)構(gòu)、跳轉(zhuǎn)語句等相關(guān)概念、原理、使用技巧與操作注意事項,需要的朋友可以參考下2020-02-02
Java利用Jsoup解析和操作HTML的技術(shù)指南
在現(xiàn)代 Java 開發(fā)中,處理 HTML 數(shù)據(jù)是一項常見需求,無論是抓取網(wǎng)頁數(shù)據(jù)、解析 HTML 文檔,還是操作 DOM 樹,Jsoup 都是一個強大的工具,本文將介紹 Jsoup 的基本功能,并通過多個詳細(xì)的代碼示例展示如何使用它解析和操作 HTML,需要的朋友可以參考下2025-03-03
盤點總結(jié)SpringBoot自帶工具類使用提升開發(fā)效率
這篇文章主要為大家介紹了盤點總結(jié)SpringBoot自帶工具類使用提升開發(fā)效率,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
SpringBoot實現(xiàn)PDF添加水印的三種方法
本文主要介紹了SpringBoot實現(xiàn)PDF添加水印的三種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07

