第14章 多線程基礎
第14章 多線程基礎
14.1 線程相關概念
14.1.1 程序(program)
是為完成特定任務,用某種語言編寫的一組指令的集合,即編寫的代碼。
示例代碼(以Java為例):
public class BallMove extends JFrame {
MyPanel mp = null;
public static void main(String[] args) {
BallMove ballMove = new BallMove();
}
public BallMove() {
mp = new MyPanel();
this.add(mp);
this.setSize(400, 300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
}
14.1.2 進程
- 運行中的程序,如啟動QQ、迅雷,操作系統會為其分配內存空間。
- 是程序的一次執行過程,有產生、存在和消亡的動態過程 。
14.1.3 線程
- 由進程創建,是進程的實體,一個進程可擁有多個線程 。
- 舉例:后續會將多線程加入“坦克大戰”游戲實踐。
14.1.4 其他相關概念
- 單線程:同一時刻僅允許執行一個線程。
- 多線程:同一時刻可執行多個線程,如QQ同時開多個聊天窗口、迅雷同時下載多個文件 。
- 并發:同一時刻多個任務交替執行,單CPU實現“貌似同時” 。
- 并行:同一時刻多個任務真正同時執行,多核CPU可實現,Java支持。
14.2 線程基本使用
14.2.1 創建線程的兩種方式(Java)
- 繼承
Thread類:重寫run方法,定義線程執行邏輯。 - 實現
Runnable接口:重寫run方法,解決Java單繼承局限,更靈活。

14.2.2 應用案例1 - 繼承Thread類
package com.ming.threaduse;
/**
* @author 明
* @version 1.0
* 演示通過繼承Thread 類創建線程
*/
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//創建Cat對象,可以當做線程使用
Cat cat = new Cat();
//讀源碼
/*
(1)
public synchronized void start() {
start0();
}
(2)
//start0() 是本地方法,是JVM調用, 底層是c/c++實現
//真正實現多線程的效果, 是start0(), 而不是 run
private native void start0();
*/
//cat.run();//run方法就是一個普通的方法, 沒有真正的啟動一個線程,就會把run方法執行完畢,才向下執行
//說明: 當main線程啟動一個子線程 Thread-0, 主線程不會阻塞, 會繼續執行
//這時 主線程和子線程是交替執行..
cat.start();//啟動線程-> 最終會執行cat的run方法
System.out.println("主線程繼續執行" + Thread.currentThread().getName());
for (int i = 0; i < 10; i++) {
System.out.println("主線程 i= " + i);
//讓主線程休眠
Thread.sleep(1000);
}
}
}
//說明
//1.當一個類繼承了Thread類,該類就可以當作線程使用
//2. 我們會重寫 run方法,寫上自己的業務代碼
//3. run Thread 類 實現了 Runnable 接口的run方法
/*
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
class Cat extends Thread{
int times = 0;
@Override
public void run() {//重寫run方法,寫上自己的業務邏輯
while (true) {
System.out.println("喵喵,我是小貓咪"+times++ + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(times==8){
break;
}
}
}
}

14.2.3 應用案例2 - 實現Runnable接口
需求:每隔1秒在控制臺輸出“hi!”,輸出10次后退出。
實現:
package com.ming.threaduse;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author 明
* @version 1.0
* 通過實現接口Runnable 來開發線程
*/
public class Thread02 {
public static void main(String[] args) throws InterruptedException {
// Dog dog = new Dog();
// //dog.start(); 這里不能調用start
// //創建了Thread對象,把 dog對象(實現Runnable),放入Thread
// Thread thread = new Thread(dog);
// thread.start();
Tiger tiger = new Tiger();
ThreadProxy threadProxy = new ThreadProxy(tiger);
threadProxy.start();
}
}
class Animal {
}
class Tiger extends Animal implements Runnable {
@Override
public void run() {
System.out.println("老虎嗷嗷叫....");
}
}
//線程代理類 , 模擬了一個極簡的Thread類
class ThreadProxy implements Runnable {//你可以把Proxy類當做 ThreadProxy
private Runnable target = null;//屬性,類型是 Runnable
@Override
public void run() {
if (target != null) {
target.run();//動態綁定(運行類型Tiger)
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start() {
start0();//這個方法時真正實現多線程方法
}
public void start0() {
run();
}
}
class Dog implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
//休眠1秒
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
if(count == 10){
break;
}
}
}
}
14..2.4線程使用應用案例-多線程執行
package com.ming.threaduse;
/**
* @author 明
* @version 1.0
* main線程啟動兩個子線程
*/
public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
}
}
class T1 implements Runnable{
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("hello,world " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10){
break;
}
}
}
}
class T2 implements Runnable{
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("hi " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5){
break;
}
}
}
}


14.3 繼承Thread vs 實現Runnable的區別
- 設計角度:
Thread類本身實現Runnable接口,本質無區別,但實現接口更靈活。 - 適用場景:實現
Runnable適合多線程共享資源,且避免單繼承限制,推薦優先使用。 - 案例:售票系統
模擬三個售票窗口售100張票,對比兩種方式,分析線程安全等問題(如資源競爭導致超賣,后續需同步機制解決 )。
package com.ming.ticket;
import com.hspedu.ticket.SellTicket;
/**
* @author 明
* @version 1.0
* 使用多線程,模擬三個窗口同時售票100張
*/
public class SellTIcket {
public static void main(String[] args) {
//測試
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
//
// //這里我們會出現超賣..
// sellTicket01.start();//啟動售票線程
// sellTicket02.start();//啟動售票線程
// sellTicket03.start();//啟動售票線程
System.out.println("===使用實現接口方式來售票=====");
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();
new Thread(sellTicket02).start();
new Thread(sellTicket02).start();
}
}
//使用Thread方式
class SellTicket01 extends Thread {
private static int ticketNum = 100;//讓多個線程共享ticketNum
@Override
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("售票結束~~");
break;
}
//休息50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一張票"
+ " 剩余票數=" + (--ticketNum));//1 - 0 - -1 - -2
}
}
}
class SellTicket02 implements Runnable {
private int ticketNum = 100;
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票結束~~");
break;
}
//休息50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一張票"
+ " 剩余票數=" + (--ticketNum));//1 - 0 - -1 - -2
}
}
}
14.4 線程終止
14.4.1 基本說明
- 線程完成任務后自動退出。
- 也可通過修改變量控制
run方法退出(通知方式),不推薦強制終止(如stop方法已棄用 )。
14.4.2 應用案例(ThreadExit.java)
需求:在main線程中停止子線程,通過標志位控制。
示例邏輯:定義子線程類,用布爾變量標記是否繼續執行,main線程觸發標記改變終止子線程。
package com.ming.exit_;
/**
* @author 明
* @version 1.0
*/
public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
//如果希望main線程去控制t1 線程的終止, 必須可以修改 loop
//讓t1 退出run方法,從而終止 t1線程 -> 通知方式
//讓主線程休眠 10 秒,再通知 t1線程退出
System.out.println("main線程休眠10s...");
Thread.sleep(10*1000);
t.setLoop(false);
}
}
class T extends Thread {
private int count = 0;
//設置一個控制變量
private boolean loop = true;
@Override
public void run() {
while (loop) {
try {
Thread.sleep(50);// 讓當前線程休眠50ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T 運行中...." + (++count));
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
14.5 線程常用方法
14.5.1 常用方法第一組
setName/getName:設置、獲取線程名稱。start:啟動線程,JVM調用run方法。run:線程執行體,直接調用是普通方法調用,非啟動新線程。setPriority/getPriority:設置、獲取線程優先級(1 - 10,默認5 )。sleep:讓當前線程休眠指定毫秒數,阻塞狀態。interrupt:中斷線程(需配合處理InterruptedException)。
14.5.2 注意事項
start才會真正啟動新線程,直接調run是普通方法調用。- 優先級僅為調度提示,不保證執行順序。
interrupt用于中斷阻塞線程,需合理處理中斷邏輯。
14.5.3 應用案例(ThreadMethod01.java)
測試上述方法,如創建線程設置名稱、優先級,演示sleep、interrupt等用法。
package com.ming.method;
/**
* @author 明
* @version 1.0
*/
public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.setName("小明");
t.setPriority(Thread.MIN_PRIORITY);
t.start();
//主線程打印5個 hi ,然后就中斷 子線程的休眠
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi " + i);
}
System.out.println(t.getName() + " 線程的優先級 =" + t.getPriority());//1
t.interrupt();//當執行到這里,就會中斷 t線程的休眠.
}
}
class T extends Thread { //自定義的線程類
@Override
public void run() {
while (true) {
for (int i = 0; i < 100; i++) {
//Thread.currentThread().getName() 獲取當前線程的名稱
System.out.println(Thread.currentThread().getName() + " 吃包子~~~~" + i);
}
try {
System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
Thread.sleep(20000);//20秒
} catch (InterruptedException e) {
//當該線程執行到一個interrupt 方法時,就會catch 一個 異常, 可以加入自己的業務代碼
//InterruptedException 是捕獲到一個中斷異常.
System.out.println(Thread.currentThread().getName() + "被 interrupt了");
}
}
}
}
14.5.4 常用方法第二組
yield:線程禮讓CPU,讓其他線程執行,禮讓時間不確定,不一定成功。join:線程插隊,調用join的線程會阻塞,直到被調用線程執行完畢。- 案例:
main線程創建子線程,按規則輸出內容,結合join控制執行順序 。
- 案例:

14.5.5 應用案例(ThreadMethod02.java)
測試yield和join:
package com.ming.method;
/**
* @author 明
* @version 1.0
*/
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();
for (int i = 1; i <= 20; i++) {
Thread.sleep(1000);//休眠1秒
System.out.println("主線程 吃了 " + i + " 包子");
if(i == 5){
System.out.println("主線程(小弟) 讓 子線程(老大) 先吃");
//join, 線程插隊
//t2.join();// 這里相當于讓t2 線程先執行完畢
Thread.yield();//禮讓,不一定成功..
System.out.println("線程(老大) 吃完了 主線程(小弟) 接著吃..");
}
}
}
}
class T2 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子線程(老大) 吃了 " + i + " 包子");
}
}
}
(演示join使子線程優先執行完部分邏輯,體會線程協作 。)
14.5.6 課堂練習
package com.ming.method;
/**
* @author 明
* @version 1.0
*/
public class ThreadMethod0Exercise {
public static void main(String[] args) throws InterruptedException {
T3 t3 = new T3();
for (int i = 1; i <= 10; i++) {
Thread.sleep(1000);//休眠1秒
System.out.println("hi " + i);
if(i == 5) {//說明主線程輸出了5次 hi
t3.start();//啟動子線程 輸出 hello...
t3.join();//立即將t3子線程,插入到main線程,讓t3先執行
}
Thread.sleep(1000);//輸出一次 hi, 讓main線程也休眠1s
}
System.out.println("主線程結束...");
}
}
class T3 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello " + i);
}
}
}
14.5.7 用戶線程和守護線程
- 用戶線程:也叫工作線程,任務執行完或通過通知方式結束。
- 守護線程:為工作線程服務,所有用戶線程結束后,守護線程自動結束,典型如垃圾回收機制 。
- 應用案例:
ThreadMethod03.java,演示將線程設置為守護線程的操作。
package com.ming.method;
/**
* @author 明
* @version 1.0
*/
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemom myDaemom = new MyDaemom();
myDaemom.setDaemon(true);
myDaemom.start();
for( int i = 1; i <= 10; i++) {//main線程
System.out.println("寶強在辛苦的工作...");
Thread.sleep(1000);
}
}
}
class MyDaemom extends Thread {
@Override
public void run() {
for(;;){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("馬蓉和宋喆快樂聊天,哈哈哈~~~");
}
}
}
14.6 線程的生命周期
14.6.1 線程狀態(JDK 中 Thread.State 枚舉表示)
- NEW:線程尚未啟動。
- RUNNABLE:線程在 Java 虛擬機中執行。
- BLOCKED:線程被阻塞,等待監視器鎖。
- WAITING:線程等待另一個線程執行特定動作。
- TIMED_WAITING:線程等待另一個線程執行動作,且有指定等待時間。
- TERMINATED:線程已退出 。

package com.ming.state_;
/**
* @author 明
* @version 1.0
*/
public class ThreadState_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName() + "狀態 "+t.getState());
t.start();
while(t.getState()!=Thread.State.TERMINATED){
System.out.println(t.getName() + "狀態 "+t.getState());
Thread.sleep(500);
}
System.out.println(t.getName() + "狀態 "+t.getState());
}
}
class T extends Thread {
@Override
public void run() {
for(int i = 1; i <= 10; i++) {
System.out.println("hi " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
14.7 線程的同步
14.7.1 問題引出(以窗口售票為例)
多線程同時操作共享資源(如售票系統的余票),可能導致數據不一致(超賣、錯賣等問題 )。
14.8 Synchronized 同步機制
14.8.1 同步原理
多線程編程中,敏感數據不允許被多個線程同時訪問,通過同步技術保證同一時刻最多一個線程訪問,確保數據完整性。也可理解為:一個線程操作內存時,其他線程無法訪問該內存地址,直到操作完成 。
14.8.2 同步具體方法
- 同步代碼塊:
synchronized (對象) { // 需同步的代碼(獲得對象鎖后執行) } - 同步方法:在方法聲明前加
synchronized,表示整個方法為同步方法:public synchronized void method(String name) { // 需同步的代碼 } - 理解類比:如同上廁所先關門(上鎖),完事再開門(解鎖),保證同一時間只有一個“使用者” 。
- 應用:用
synchronized解決售票問題,避免多線程競爭導致的錯誤。
14.9 分析同步原理

14.10 互斥鎖
14.10.1 基本介紹
- Java 引入對象互斥鎖,保證共享數據操作完整性,每個對象對應一個“互斥鎖”標記,確保同一時刻只有一個線程訪問該對象。
synchronized關鍵字與互斥鎖關聯,修飾對象時,標記對象同一時刻只能被一個線程訪問。- 同步局限性:會降低程序執行效率;同步方法(非靜態)鎖對象默認是
this,也可為其他同一對象;靜態同步方法鎖對象是當前類的class對象 。
14.10.2使用互斥鎖來解決售票問題
package com.ming.syn;
/**
* @author 明
* @version 1.0
* 使用多線程,模擬三個窗口同時售票100張
*/
public class SellTIcket {
public static void main(String[] args) {
//測試
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
//
// //這里我們會出現超賣..
// sellTicket01.start();//啟動售票線程
// sellTicket02.start();//啟動售票線程
// sellTicket03.start();//啟動售票線程
// System.out.println("===使用實現接口方式來售票=====");
// SellTicket02 sellTicket02 = new SellTicket02();
//
// new Thread(sellTicket02).start();
// new Thread(sellTicket02).start();
// new Thread(sellTicket02).start();
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
}
}
//實現接口方式,使用synchronized實現線程同步
class SellTicket03 implements Runnable {
private int ticketNum = 100;
private boolean loop = true;
private Object lock = new Object();
//同步方法(靜態的)的鎖為當前類本身
//解讀
//1. public synchronized static void m1() {} 鎖是加在 SellTicket03.class
//2. 如果在靜態方法中,實現一個同步代碼塊.
/*
synchronized (SellTicket03.class) {
System.out.println("m2");
}
*/
public synchronized static void m1() {
}
public static void m2() {
synchronized (SellTicket03.class) {
System.out.println("m2");
}
}
//說明
//1. public synchronized void sell() {} 就是一個同步方法
//2. 這時鎖在 this對象
//3. 也可以在代碼塊上寫 synchronize ,同步代碼塊, 互斥鎖還是在this對象
public void sell(){//同步方法,在同一時刻,只能有一個線程執行run方法
synchronized (lock) {
if (ticketNum <= 0) {
System.out.println("售票結束~~");
loop = false;
return;
}
//休息50ms
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一張票"
+ " 剩余票數=" + (--ticketNum));//1 - 0 - -1 - -2
}
}
@Override
public void run() {
while (loop) {
sell();
}
}
}
//使用Thread方式
class SellTicket01 extends Thread {
private static int ticketNum = 100;//讓多個線程共享ticketNum
@Override
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("售票結束~~");
break;
}
//休息50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一張票"
+ " 剩余票數=" + (--ticketNum));//1 - 0 - -1 - -2
}
}
}
class SellTicket02 implements Runnable {
private int ticketNum = 100;
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票結束~~");
break;
}
//休息50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一張票"
+ " 剩余票數=" + (--ticketNum));//1 - 0 - -1 - -2
}
}
}
14.10.3 注意事項和細節
- 同步方法未用
static修飾,默認鎖對象是this;用static修飾,默認鎖對象是當前類.class。 - 實現步驟:分析上鎖代碼 → 選擇同步代碼塊或同步方法 → 確保多個線程鎖對象相同 。
14.11 線程的死鎖
14.11.1 基本介紹
多個線程互相占用對方所需的鎖資源,且都不釋放,導致程序“卡死”,編程需避免 。
14.11.2 應用案例(生活場景)
媽媽要求“完成作業才讓玩手機”,小明要求“讓玩手機才完成作業”,雙方互不相讓,形成死鎖 。
14.11.3 應用案例(代碼)
DeadLock_.java,演示代碼中線程因互相等待鎖資源導致死鎖的場景 。
package com.ming.syn;
/**
* @author 明
* @version 1.0
*/
public class DeadLock_ {
public static void main(String[] args) {
DeadLockDemo a = new DeadLockDemo(true);
DeadLockDemo b = new DeadLockDemo(false);
a.setName("a線程");
b.setName("b線程");
a.start();
b.start();
}
}
//線程
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保證多線程,共享一個對象,這里使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//構造器
this.flag = flag;
}
@Override
public void run() {
//下面業務邏輯的分析
//1. 如果flag 為 T, 線程A 就會先得到/持有 o1 對象鎖, 然后嘗試去獲取 o2 對象鎖
//2. 如果線程A 得不到 o2 對象鎖,就會Blocked
//3. 如果flag 為 F, 線程B 就會先得到/持有 o2 對象鎖, 然后嘗試去獲取 o1 對象鎖
//4. 如果線程B 得不到 o1 對象鎖,就會Blocked
if (flag) {
synchronized (o1) {//對象互斥鎖, 下面就是同步代碼
System.out.println(Thread.currentThread().getName() + " 進入1");
synchronized (o2) { // 這里獲得li對象的監視權
System.out.println(Thread.currentThread().getName() + " 進入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 進入3");
synchronized (o1) { // 這里獲得li對象的監視權
System.out.println(Thread.currentThread().getName() + " 進入4");
}
}
}
}
}
14.12 釋放鎖
14.12.1 會釋放鎖的操作
- 同步方法/代碼塊執行結束(如“上完廁所出來” )。
- 同步代碼塊/方法中遇到
break、return(如“沒正常完事,但因特殊情況退出” )。 - 同步代碼塊/方法中出現未處理的
Error或Exception,導致異常結束(如“忘帶紙,被迫出來” )。 - 同步代碼塊/方法中執行
wait()方法,當前線程暫停并釋放鎖(如“覺得需要醞釀,出來等會再進去” )。
14.12.2 不會釋放鎖的操作
- 線程執行同步代碼塊/方法時,調用
Thread.sleep()、Thread.yield()暫停執行(如“上廁所時在坑位上瞇一會” ,不釋放鎖 )。 - 其他線程調用當前線程的
suspend()方法掛起線程(suspend()、resume()方法已不推薦使用,易導致死鎖等問題 )。
14.13 本章作業
1. Homework01.java(5分鐘)
- 需求:
main方法啟動兩個線程,第一個線程循環隨機打印 100 以內整數,直到第二個線程從鍵盤讀取到“Q”命令停止。
public class Homework01 {
public static void main(String[] args) {
A a = new A();
B b = new B(a);//一定要注意.
a.start();
b.start();
}
}
//創建A線程類
class A extends Thread {
private boolean loop = true;
@Override
public void run() {
//輸出1-100數字
while (loop) {
System.out.println((int)(Math.random() * 100 + 1));
//休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("a線程退出...");
}
public void setLoop(boolean loop) {//可以修改loop變量
this.loop = loop;
}
}
//直到第2個線程從鍵盤讀取了“Q”命令
class B extends Thread {
private A a;
private Scanner scanner = new Scanner(System.in);
public B(A a) {//構造器中,直接傳入A類對象
this.a = a;
}
@Override
public void run() {
while (true) {
//接收到用戶的輸入
System.out.println("請輸入你指令(Q)表示退出:");
char key = scanner.next().toUpperCase().charAt(0);
if(key == 'Q') {
//以通知的方式結束a線程
a.setLoop(false);
System.out.println("b線程退出.");
break;
}
}
}
}
2. Homework02.java(5分鐘)
- 需求:模擬 2 個用戶從同一銀行卡取錢(總額 10000),每次取 1000,余額不足則無法取款,需避免超取(解決線程同步問題 )。
(作業需結合多線程、同步機制等知識實現,重點練習線程協作、資源競爭處理 。)
package com.ming.homework;
/**
* @author 明
* @version 1.0
*/
public class Homework02 {
public static void main(String[] args) {
Account account = new Account();
Thread t1 = new Thread(account);
Thread t2 = new Thread(account);
t1.setName("小明");
t2.setName("張三");
t1.start();
t2.start();
}
}
//編程取款的線程
//1.因為這里涉及到多個線程共享資源,所以我們使用實現Runnable方式
//2. 每次取出 1000
class Account implements Runnable {
private int balance = 10000;
private boolean isRunning = true;
private void drawMoney() {
//解讀
//1. 這里使用 synchronized 實現了線程同步
//2. 當多個線程執行到這里時,就會去爭奪 this對象鎖
//3. 哪個線程爭奪到(獲取)this對象鎖,就執行 synchronized 代碼塊, 執行完后,會釋放this對象鎖
//4. 爭奪不到this對象鎖,就blocked ,準備繼續爭奪
//5. this對象鎖是非公平鎖.
synchronized (this) {
if (balance -1000 <= 0) {
System.out.println("余額不足,不能取款了");
isRunning = false;
} else{
balance = balance - 1000;
System.out.println(Thread.currentThread().getName() + "正在取錢,取完后還剩"+balance);
}
}
}
@Override
public void run() {
while (isRunning) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
drawMoney();
}
}
}

浙公網安備 33010602011771號