Java基礎進階_day16_(多線程,線程池,定時器,設計模式)
1. 多線程
1.1 線程死鎖
線程死鎖:是指兩個或者兩個以上的線程在執行的過程中,因爭奪資源產生的一種互相等待現象.多由同步代碼塊的嵌套產生.
死鎖案例
public class My_DeadLock_Demo01 {
public static void main(String[] args) {
// 死鎖驗證,方式1
// Object obj1 = new Object();
// Object obj2 = new Object();
// DeadLock dl = new DeadLock(true, obj1, obj2);
// DeadLock dl2 = new DeadLock(false, obj1, obj2);
// dl.start();
// dl2.start();
// 死鎖驗證,方式2
DeadLock2 dl2 = new DeadLock2();
Thread t1 = new Thread(dl2);
Thread t2 = new Thread(dl2);
t1.start();
t2.start();
}
}
// 死鎖驗證方式1
class DeadLock extends Thread {
// 定義兩把鎖對象
private Object obj1 = new Object();
private Object obj2 = new Object();
private boolean flag = true;
public DeadLock(boolean flag, Object obj1, Object obj2) {
this.flag = flag;
this.obj1 = obj1;
this.obj2 = obj2;
}
@Override
public void run() {
if (flag) {
synchronized (obj1) {
System.out.println("if obj1");
synchronized (obj2) {
System.out.println("if obj2");
}
}
} else {
synchronized (obj2) {
System.out.println("else obj2");
synchronized (obj1) {
System.out.println("else obj1");
}
}
}
}
}
// 死鎖驗證方式2
class DeadLock2 implements Runnable {
// 定義兩把鎖對象
private Object obj1 = new Object();
private Object obj2 = new Object();
@Override
public void run() {
if (Math.random() > 0.5) {
synchronized (obj1) {
System.out.println("同步代碼塊1,obj1");
synchronized (obj2) {
System.out.println("同步代碼塊1,obj2");
}
}
} else {
synchronized (obj2) {
System.out.println("同步代碼塊2,obj2");
synchronized (obj1) {
System.out.println("同步代碼塊2,obj1");
}
}
}
}
}
1.2 互斥鎖
Lock接口:Lock實現提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作.
方法:將需要進行同步的線程操作代碼使用下面的兩個方法進行限定:
void lock():獲取鎖;
void unlock():釋放鎖.
案例代碼
public class My_Lock_Demo01 {
public static void main(String[] args) {
SellTickets s = new SellTickets();
Thread t1 = new Thread(s, "窗口1");
Thread t2 = new Thread(s, "窗口2");
Thread t3 = new Thread(s, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
class SellTickets implements Runnable {
private int tickets = 100;
// 定義鎖對象
private ReentrantLock rl = new ReentrantLock();
@Override
public void run() {
// 獲取鎖
try {
rl.lock();
while (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售賣第"
+ (tickets--) + "張票");
}
} finally {
// 釋放鎖
rl.unlock();
}
}
}
1.3 多線程間的通信
線程間通信:不同種類的線程針對同一種資源進行操作,不同種類的線程使用的鎖對象相同.
線程間的等待喚醒機制
方式1
# 等待喚醒機制針對的是鎖對象,方法定義在Object類中.
* public final void wait():將當前使用的鎖對象的線程設置爲等待狀態.
wait()方法執行時,將對象釋放並在停止地方一直等待,當被喚醒時,會從等待的地方繼續向後執行.
* public final void notify():喚醒在此鎖對象監視器上等待的單個線程,喚醒是隨機的.
notify()方法喚醒其他線程,只是通知其他線程,其他線程的執行還是需要搶佔CPU的執行權.
* public final void notifyAll():喚醒在此鎖對象監視器上等待的所有線程.
案例代碼
/*
*使用同步代碼塊的等待喚醒機制
*/
public class My_ThreadCommunication_Demo01 {
public static void main(String[] args) {
// 不同的線程使用共享的數據
Person p = new Person();
// 操作數據對象
SetInfo si = new SetInfo(p);
GetInfo gi = new GetInfo(p);
// 操作數據的線程
Thread t1 = new Thread(si);
Thread t2 = new Thread(gi);
// 啓動線程
t1.start();
t2.start();
}
}
// 設置數據信息
class SetInfo implements Runnable {
private Person p = null;
int num = 1;
public SetInfo(Person p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
// 設置等待,當數據爲空時,將線程設爲等待
if(p.flag) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (num % 2 == 0) {
p.name = "張三";
p.age = 23;
} else {
p.name = "李四";
p.age = 24;
}
num++;
// 當數據源有數據時,將該鎖對象上的其他線程喚醒
p.flag = true;
p.notify();
}
}
}
}
// 獲取數據信息
class GetInfo implements Runnable {
private Person p = null;
public GetInfo(Person p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
if(!p.flag) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(p.name + "..." + p.age);
p.flag = false;
p.notify();
}
}
}
}
// 原始數據類
class Person {
String name;
int age;
// 定義是否有數據的標識
boolean flag = false;
}
/*
* 使用同步方法的方式實現等待喚醒機制.
*/
public class My_ThreadCommunication_Demo02 {
public static void main(String[] args) {
// 不同的線程使用共享的數據
Person2 p = new Person2();
// 操作數據對象
SetInfo2 si = new SetInfo2(p);
GetInfo2 gi = new GetInfo2(p);
// 操作數據的線程
Thread t1 = new Thread(si);
Thread t2 = new Thread(gi);
// 啓動線程
t1.start();
t2.start();
}
}
// 設置數據信息
class SetInfo2 implements Runnable {
private Person2 p = null;
int num = 1;
public SetInfo2(Person2 p) {
this.p = p;
}
@Override
public void run() {
while (true) {
if (num % 2 == 0) {
p.setData("張三", 23);
} else {
p.setData("李四", 24);
}
num++;
}
}
}
// 獲取數據信息
class GetInfo2 implements Runnable {
private Person2 p = null;
public GetInfo2(Person2 p) {
this.p = p;
}
@Override
public void run() {
while (true) {
p.getData();
}
}
}
// 原始數據類
class Person2 {
private String name;
private int age;
// 定義是否有數據的標識
private boolean flag = false;
// 使用同步的方法獲取和設置數據的值
// 鎖對象是本類的實例
public synchronized void setData(String name, int age) {
if (this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.age = age;
this.flag = true;
this.notify();
}
public synchronized void getData() {
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.name + "..." + this.age);
this.flag = false;
this.notify();
}
}
方式2
# ReentrantLock類是Lock接口的實現類.
* 多線程間的等待喚醒機制:
使用ReentrantLock類的newCondition()方法可以獲取Condition對象;
需要等待的時候使用Condition的await()方法, 喚醒的時候用signal()方法;
不同的線程使用不同的Condition, 這樣就能區分喚醒的時候找哪個線程了.
案例代碼
/*
* ReentrantLock類:多線程間的等待喚醒機制
*/
public class My_Lock_Demo02 {
public static void main(String[] args) {
final Printer p = new Printer();
// 創建線程1
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
// 創建線程2
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
// 創建線程3
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer {
// 定義Lock鎖對象
private ReentrantLock rl = new ReentrantLock();
// 定義第一個方法的Condition
Condition c1 = rl.newCondition();
// 定義第二個方法的Condition
Condition c2 = rl.newCondition();
// 定義第三個方法的Condition
Condition c3 = rl.newCondition();
// 定義方法執行標誌
private int flag = 1;
public void print1() throws InterruptedException {
rl.lock();
if (flag != 1) {
// 該線程等待
c1.await();
}
System.out.print("張");
System.out.print("三");
System.out.println();
flag = 2;
// 喚醒c2對應的線程
c2.signal();
rl.unlock();
}
public void print2() throws InterruptedException {
rl.lock();
if (flag != 2) {
c2.await();
}
System.out.print("李");
System.out.print("四");
System.out.println();
flag = 3;
c3.signal();
rl.unlock();
}
public void print3() throws InterruptedException {
rl.lock();
if (flag != 3) {
c3.await();
}
System.out.print("王");
System.out.print("五");
System.out.println();
flag = 1;
c1.signal();
rl.unlock();
}
}
1.4 線程組
- 線程組:Java中使用ThreadGroup來表示線程組,它可以對一批線程進行分類管理,Java允許程序直接對線程組進行控制。所有線程默認的是屬於主線程組.
- Thread類中:
- public final ThreadGroup getThreadGroup():返回該線程所屬的線程組;
- public Thread(ThreadGroup group,Runnable target):將創建的線程對象添加到指定的線程組中.
- ThreadGroup類:
- public ThreadGroup(String name):構造一個新線程組;
- public final String getName():返回此線程組的名稱;
- public final void setDaemon(boolean daemon):將此線程組的所有線程設爲守護線程;
- public final void setMaxPriority(int pri):設置線程組的最高優先級;
- public final void destroy():銷燬此線程組及其所有子組.
- 例如:
獲取當前線程所屬的線程組
System.out.println(Thread.currentThread().getThreadGroup().getName()); // main
1.5 多線程總結
# 多線程常見問題總結:
* 1.多線程常見實現方式:
A:繼承Thread類,重寫run方法,在run方法中定義需要線程執行的代碼,然後使用start方法啓動線程;
B:實現Runnable接口,重寫run方法,在run方法中定義需要執行的代碼,然後作爲Thread實例的參數,再使用start方法啓動線程;
C:Callable接口實現,需要依賴線程池啓動線程.
* 2.線程的同步方式:
A:同步代碼塊
同步代碼塊的鎖對象是任意對象;
靜態代碼塊的鎖對象是.class文件
B:同步方法
同步方法的鎖對象是this
* 3.run()和start()區別:
run()方法是定義需要線程執行的代碼,直接調用和普通方法效果相同;
start()方法是啓動線程,由jvm調用線程的run()方法.
* 4.sleep()和wait()方法的區別:
sleep()方法:必須指定線程的休眠時間,休眠時間到了則會繼續執行程序,並且在同步方法或同步代碼塊中不釋放鎖對象;
wait()方法:參數的時間可有可無,參數的含義是多長時間後開始等待,並且在同步方法或同步代碼塊中會釋放鎖對象.
* 5.wait(),notify()和notifyall()方法均定義在Object類中:
由於同步代碼塊的鎖對象是任意對象,而這些方法都是由鎖對象進行調用的,所以將這些方法均定義在Object中.
* 6.線程的生命週期圖(常見的兩種)
新建-->就緒-->運行-->死亡
新建-->就緒-->運行-->阻塞-->就緒-->運行-->死亡
* 7.線程的狀態轉換圖
新建-->就緒-->運行-->死亡
新建-->就緒-->運行-->就緒-->運行-->死亡
新建-->就緒-->運行-->同步阻塞-->就緒--運行-->死亡
新建-->就緒-->運行-->等待阻塞-->同步阻塞-->就緒-->運行-->死亡
新建-->就緒-->其他阻塞-->就緒-->運行-->死亡
線程狀態轉換圖
2. 線程池
線程池:使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,更應該考慮使用線程池.
線程池特點:
線程池裏的每一個線程代碼結束後,並不會死亡,而是再次回到線程池中成爲空閒狀態,等待下一個對象來使用.
JDK5版本增加工廠類創建線程池
# Executors類:
public static ExecutorService newCachedThreadPool():創建帶有緩衝區功能的線程池;
public static ExecutorService newFixedThreadPool(int nThreads):創建含有指定個數線程的線程池;
public static ExecutorService newSingleThreadExecutor():創建單個線程的線程池.
這些方法的返回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程.它提供瞭如下方法:
Future<?> submit(Runnable task):提交runnable方式的任務;
<T> Future<T> submit(Callable<T> task):提交callable方式的任務;
案例代碼
public class My_ThreadPool_Demo01 {
public static void main(String[] args) {
// 創建線程池對象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 提交任務給線程池進行執行
pool.submit(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()
+ ":" + i);
}
}
}
});
// 關閉線程池
pool.shutdown();
}
}
實現Callable接口的方式創建線程
# 線程實現的Callable接口的方式:
* Callable接口:返回結果並且可能拋出異常的任務,實現者定義了一個不帶任何參數的叫做call的方法.
* Callable接口實現的線程只能通過線程池進行調用.
# 好處:
* 可以有返回值;
* 可以拋出異常.
# 弊端:
* 調用依賴線程池;
* 代碼較爲複雜,一般不使用.
案例代碼
public class My_ThreadPool_Demo02 {
public static void main(String[] args) throws Exception, ExecutionException {
MyThread mt = new MyThread(8);
MyThread mt2 = new MyThread(9);
ExecutorService pool = Executors.newFixedThreadPool(2);
// pool提交任務後可以通過Future異步的方式獲取結果
Future<Integer> f1 = pool.submit(mt);
Future<Integer> f2 = pool.submit(mt2);
System.out.println(f1.get() + ".." + f2.get());
// 關閉線程池
pool.shutdown();
}
}
class MyThread implements Callable<Integer> {
private int num = 0;
public MyThread(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 1;
for (int i = 1; i <= num; i++) {
sum = sum * i;
}
return sum;
}
}
3. 定時器
定時器:定時器可用於調度多個定時任務以後臺線程的方式執行.在Java中,可以通過Timer和TimerTask類來實現定義調度的功能.
# Timer類:Timer
* 構造方法:public Timer():創建一個計時器.
* 成員方法:
public void schedule(TimerTask task,Date time):安排在指定的時間執行指定的任務;
public void schedule(TimerTask task, long delay):創建在延遲指定毫秒值後執行的任務;
public void schedule(TimerTask task,long delay,long period):創建指定延遲時間並間隔period重複執行任務.
# TimerTask抽象類:
public abstract void run():定義計時器要執行的任務;
public boolean cancel():取消任務.
案例代碼
public class My_Timer_Demo01 {
public static void main(String[] args) {
Timer t = new Timer();
// 開啓定時器
t.schedule(new MyTimer(t), 2000, 2000);
}
}
// 定時器任務類
class MyTimer extends TimerTask {
private Timer t = null;
public MyTimer(Timer t) {
this.t = t;
}
@Override
public void run() {
System.out.println("hello somnus");
// 終止計時器
t.cancel();
}
}
4. 設計模式
4.1 設計模式概述
# 設計模式:設計模式(Design pattern)是一套被反覆使用,多數人知曉的,經過分類編目的,代碼設計經驗的總結.
* 使用設計模式是爲了可重用代碼,讓代碼更容易被他人理解,保證代碼可靠性.
* 設計模式不是一種方法和技術,而是一種思想.
* 設計模式和具體的語言無關,學習設計模式就是要建立面向對象的思想,儘可能的面向接口編程,低耦合,高內聚,使設計的程序可複用.
4.2 設計模式分類
# 設計模式的分類
* 創建型模式:對象的創建
* 結構型模式:對象的組成(結構)
* 行爲型模式 :對象的行爲
# 常見的設計模式:
* 創建型模式:簡單工廠模式,工廠方法模式,抽象工廠模式,建造者模式,原型模式,單例模式.(6個)
* 結構型模式:外觀模式,適配器模式,代理模式,裝飾模式,橋接模式,組合模式,享元模式.(7個)
* 行爲型模式:模版方法模式,觀察者模式,狀態模式,職責鏈模式,命令模式,訪問者模式,策略模式,備忘錄模式,迭代器模式,解釋器模式.(10個)
4.2.1 簡單工廠模式
# 簡單工廠模式:
* 又叫靜態工廠方法模式,它定義一個具體的工廠類負責創建一些類的實例.
# 優點:
* 客戶端不需要在負責對象的創建,從而明確了各個類的職責.
# 缺點:
* 這個靜態工廠類負責所有對象的創建,如果有新的對象增加,或者某些對象的創建方式不同,就需要不斷的修改工廠類,不利於後期的維護.
案例代碼
public class PersonDemo {
public static void main(String[] args) {
Person p = PersonFactory.creatStudent();
Person p2 = PersonFactory.creatTeacher();
p.eat();
p2.eat();
}
}
abstract class Person {
public abstract void eat();
}
class Student extends Person {
@Override
public void eat() {
System.out.println("學生吃飯");
}
}
class Teacher extends Person {
@Override
public void eat() {
System.out.println("教師吃飯");
}
}
class PersonFactory {
private PersonFactory() {
}
public static Student creatStudent() {
return new Student();
}
public static Teacher creatTeacher() {
return new Teacher();
}
}
4.2.2 工廠方法模式
# 工廠方法模式:
* 工廠方法模式中抽象工廠類負責定義創建對象的接口,具體對象的創建工作由繼承抽象工廠的具體類實現.
# 優點:
* 客戶端不需要在負責對象的創建,從而明確了各個類的職責,如果有新的對象增加,只需要增加一個具體的類和具體的工廠類即可,
* 不影響已有的代碼,後期維護容易,增強了系統的擴展性.
# 缺點:
* 需要額外的編寫代碼,增加了工作量.
案例代碼
public class AnimalDemo {
public static void main(String[] args) {
Factory f1 = new CreatDog();
Animal dog = f1.creatAnimal();
Factory f2 = new CreatCat();
Animal cat = f2.creatAnimal();
dog.eat();
cat.eat();
}
}
abstract class Animal {
public abstract void eat();
}
interface Factory{
public abstract Animal creatAnimal();
}
class CreatDog implements Factory {
@Override
public Animal creatAnimal() {
return new Dog();
}
}
class CreatCat implements Factory {
@Override
public Animal creatAnimal() {
return new Cat();
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨頭");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("貓吃魚");
}
}
4.2.3 單例模式
# 單例模式:API中的Runtime類是單例模式
* 單例模式就是要確保類在內存中只有一個對象,該實例必須自動創建,並且對外提供.
* 優點:
在系統內存中只存在一個對象,因此可以節約系統資源,對於一些需要頻繁創建和銷燬的對象單例模式無疑可以提高系統的性能.
* 缺點:
沒有抽象層,因此擴展很難;
職責過重,在一定程序上違背了單一職責.
# 單例模式分類:選用餓漢式
* 餓漢式:類在加載的時候創建對象
* 懶漢式:調用的時候才創建對象
* 聲明靜態常量的方式.
# 單例模式的思想和實現方式:
* 單例的思想是保證類在內存中只有一個對象.
* 實現方式:
餓漢式:其操作是原子性的不會出現線程安全問題;
懶漢式:延遲加載,可能出現線程安全問題(多條語句操作共享數據,解決方法是使用同步方法).
案例代碼
public class My_SingleObject_Demo {
public static void main(String[] args) {
}
}
// 餓漢式單例
class SingleObject {
// 私有構造方法
private SingleObject(){
}
// 創建本類的實例對象
public static SingleObject s = new SingleObject();
// 提供獲取該類對象的方法
public static SingleObject getSingleObject() {
return s;
}
}
// 懶漢式單例
class SingleObject2 {
// 私有構造方法
private SingleObject2(){
}
// 定義本類的實例對象
public static SingleObject2 s = null;
// 提供獲取該類對象的方法
// 方式1,將方法定義爲同步方法,避免線程安全問題
public synchronized static SingleObject2 getSingleObject() {
// 有兩條語句操作共享數據s,則使用同步方法
if(s == null) {
s = new SingleObject2();
}
return s;
}
// 方式2,雙重判斷,避免線程安全問題
/*public static SingleObject2 getSingleObject() {
// 有兩條語句操作共享數據s,則使用同步方法
if(s == null) {
synchronized(SingleObject2.class){
if(s == null) {
s = new SingleObject2();
}
}
}
return s;
}*/
}
// 靜態常量的單例模式
class SingleObject3 {
// 私有構造方法
private SingleObject3(){
}
// 創建本類的靜態常量
public static final SingleObject3 so = new SingleObject3();
}
4.2.4 模板設計模式
# 模板設計模式:
* 模版方法模式就是定義一個算法的骨架,而將具體的算法延遲到子類中來實現.
# 優點:
* 使用模版方法模式,在定義算法骨架的同時,可以很靈活的實現具體的算法,滿足用戶靈活多變的需求.
# 缺點:
* 如果算法骨架有修改的話,則需要修改抽象類.
案例代碼
public class My_Templet_Demo {
public static void main(String[] args) {
// 測試運行某段代碼的時間
GetTime gt = new GetTime() {
@Override
public void code() {
for (int i = 0; i < 100000; i++) {
System.out.println(i);
}
}
};
// 獲取代碼運行的時間
long time = gt.getTime();
System.out.println(time+"毫秒");
}
}
// 定義計算某段代碼運行的時間
abstract class GetTime {
public long getTime() {
// 初始時間
long start = System.currentTimeMillis();
// 計算的代碼
this.code();
// 程序運行的時間
long end = System.currentTimeMillis();
// 將時間返回
return end - start;
}
// 需要被計算時間的代碼
public abstract void code();
}
4.2.5 裝飾設計模式
# 裝飾設計模式:
* 裝飾模式就是使用被裝飾類的一個子類的實例,在客戶端將這個子類的實例交給裝飾類,是繼承的替代方案.
# 優點:
* 使用裝飾模式,可以提供比繼承更靈活的擴展對象的功能,它可以動態的添加對象的功能,並且可以隨意的組合這些功能.
# 缺點:
* 正因爲可以隨意組合,所以就可能出現一些不合理的邏輯.
# 關係:
* 接口 ---> 實現接口的抽象類
* | |
* 實現接口的子類 不同的裝飾類
案例代碼
public class My_Decorate_Demo {
public static void main(String[] args) {
// 普通類測試
Phone p = new HuaWei();
p.call();
// 裝飾彩鈴
PhoneDecorate pd = new RingPhoneDecorate(p);
pd.call();
// 裝飾音樂
PhoneDecorate pd2 = new MusicPhoneDecorate(p);
pd2.call();
// 先裝飾彩鈴,再裝飾音樂
PhoneDecorate pd3 = new RingPhoneDecorate(pd2);
pd3.call();
}
}
// 定義接口
interface Phone {
// 定義打電話的功能
public abstract void call();
}
// 定義實現接口的類
class HuaWei implements Phone {
@Override
public void call() {
System.out.println("手機打電話");
}
}
// 定義實現接口的抽象類
abstract class PhoneDecorate implements Phone {
private Phone p;
public PhoneDecorate(Phone p) {
this.p = p;
}
// 重寫call方法,並調用p的call方法
@Override
public void call() {
this.p.call();
}
}
// 定義具體的裝飾類
class RingPhoneDecorate extends PhoneDecorate {
public RingPhoneDecorate(Phone p) {
super(p);
}
// 重寫call方法,加入要裝飾的內容
@Override
public void call() {
System.out.println("手機彩鈴");
super.call();
}
}
// 定義裝飾音樂的裝飾類
class MusicPhoneDecorate extends PhoneDecorate {
public MusicPhoneDecorate(Phone p) {
super(p);
}
// 重寫call方法,加入要裝飾的內容
@Override
public void call() {
super.call();
System.out.println("手機聽音樂");
}
}