- 進程:一個操作系統(tǒng)中可以同時運行多個任務(wù)(程序)。系統(tǒng)級別上的多線程(多個任務(wù))跪呈,每個任務(wù)就叫做一個進程段磨。
- 線程:一個程序同時可能運行多個任務(wù)。那么每個任務(wù)就叫做一個線程耗绿。
- 并發(fā):線程是并發(fā)運行的薇溃。操作系統(tǒng)將時間劃分為若干個片段(時間片),盡可能的均勻分配給每一個任務(wù)缭乘,被分配時間片后沐序,任務(wù)有機會被cpu所執(zhí)行琉用。隨著cpu高效的運行,宏觀上看所有任務(wù)都在運行策幼。但微觀上看邑时,每個任務(wù)都是走走停停的。這種現(xiàn)象稱之為并發(fā)特姐。
Thread類——線程類
- thread類的實例代表一個并發(fā)任務(wù)晶丘。
并發(fā)的任務(wù)邏輯是通過重寫Thread的run方法實現(xiàn)的。 - 線程調(diào)度:線程調(diào)度機制會將所有并發(fā)任務(wù)做統(tǒng)一的調(diào)度工作唐含,劃分時間片(可以被cpu執(zhí)行的時間)給每一個任務(wù)浅浮,時間片盡可能均勻,但做不到絕對均勻捷枯。同時滚秩,被分配時間片后,該任務(wù)被cpu執(zhí)行淮捆,但調(diào)度的過程中不能保證所有任務(wù)都是平均的獲取時間片的次數(shù)郁油。只能做到盡可能平均。這兩個都是程序不可控的攀痊。
/**
* 線程
*
* 實現(xiàn)線程需要兩步
* 1:繼承自Thread
* 2:重寫run方法
* run方法中應(yīng)該定義我們需要并發(fā)執(zhí)行的任務(wù)邏輯
*/
public class MyFirstThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(i);
}
}
}
public class MySecThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("你好"+i+"次");
}
}
}
- 另一種創(chuàng)建線程的方式桐腌,將線程與執(zhí)行的邏輯分離開。因為有了這樣的設(shè)計苟径,才有了線程池案站。關(guān)注點在于要執(zhí)行的邏輯。
- Runnable接口:
用于定義線程要執(zhí)行的任務(wù)邏輯棘街。我們定義一個類實現(xiàn)Runnable接口嚼吞,這時我們必須重寫run方法。在其中定義我們要執(zhí)行的邏輯蹬碧。之后將Runnable交給線程去執(zhí)行舱禽。從而實現(xiàn)了線程與其執(zhí)行的任務(wù)分離開。 - 解耦:線程與線程體解耦恩沽。打斷依賴關(guān)系
public class MyFirstRunnable implements Runnable {
/**
* run方法中定義線程要執(zhí)行的邏輯
*/
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(i);
}
}
}
public class MySecRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("你好"+i+"次");
}
}
}
/**
* 測試線程
*/
public class TestThread {
public static void main(String[] args){
/*測試并發(fā)操作多個任務(wù)*/
Thread t1 = new MyFirstThread();
Thread t2 = new MySecThread();
/*啟動線程開始并發(fā)執(zhí)行任務(wù)
* 注意誊稚!想并發(fā)操作不要直接調(diào)用run方法!而是調(diào)用線程的
* start()方法啟動線程罗心。
* */
//t1.start();
//t2.start();
/*不要使用stop()方法來停止線程的運行里伯。這是不安全的操作
* 想讓線程停止,應(yīng)該通過run方法的執(zhí)行完畢來進行自然結(jié)束
* (可以通過加標志位的方式來讓run方法提前結(jié)束來停止線程)渤闷。
* */
//t1.stop();
Runnable r1 = new MyFirstRunnable();
Runnable r2 = new MySecRunnable();
/*將兩個任務(wù)分別交給線程去并發(fā)處理
* 將任務(wù)交給線程可以使用線程的重載構(gòu)造方法
* Thread(Runnable runnable)
* */
Thread thread1 = new Thread(r1);
Thread thread2 = new Thread(r2);
//thread1.start();
//thread2.start();
/*使用匿名內(nèi)部類方式創(chuàng)建線程*/
Thread td1 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
}
};
//實現(xiàn)Runnable接口的形式
Thread td2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("你好"+i+"次");
}
}
});
td1.start();
td2.start();
}
}
線程休眠
/**
* 線程睡眠阻塞
* 阻塞:
* 使當前線程放棄cpu時間疾瓮,進入阻塞狀態(tài)。在阻塞狀態(tài)的線程
* 不會分配時間片飒箭。直到該線程結(jié)束阻塞狀態(tài)回到Runnable狀態(tài)
* 方可再次獲得時間片來讓cpu運行(進入Running狀態(tài))
*/
public class ThreadSleep {
public static void main(String[] args){
/*
* 讓當前線程主動進入阻塞狀態(tài)
* Thread.sleep(long time)
* 主動進入阻塞狀態(tài)time毫秒后回到Runnable狀態(tài)
* */
int i = 0;
while (true){
System.out.println(i+"秒");
i++;
try {
/*
* 使用Thread.sleep()方法阻塞線程時強制讓我們必須捕獲“中斷異忱堑纾”
* 引發(fā)情況:
* 當前線程處于Sleep阻塞期間蜒灰,被另一個線程
* 中斷阻塞狀態(tài)時,當前線程會拋出該異常肩碟。
* */
//阻塞當前主線程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 中斷異常演示:仿裝修小品
*/
public class InterruptedExceptionTest {
public static void main(String[] args){
/*
* 林永健進入睡眠狀態(tài)
*
* 方法中定義的類叫做局部內(nèi)部類
* 局部內(nèi)部類中若想引用當前方法的其它局部變量
* (參數(shù)也是方法的局部變量)强窖,
* 那么該變量必須是final的。
* */
final Thread lin = new Thread(){
@Override
public void run() {
System.out.println("林:睡覺了削祈。翅溺。。");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
System.out.println("林:干嘛呢髓抑!干嘛呢咙崎!干嘛呢!");
System.out.println("林:都破了相了");
}
}
};
lin.start();//啟動第一個線程
Thread huang = new Thread(){
@Override
public void run() {
System.out.println("黃:80一錘子吨拍,您說砸哪兒褪猛?");
for (int i = 0; i < 5; i++) {
System.out.println("黃:80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("咣當!");
System.out.println("黃:搞定密末!");
lin.interrupt();//中斷第一個線程的阻塞狀態(tài)
}
};
huang.start();//啟動第一個線程
}
}
線程其它方法
/**
* 線程的方法:
* yield():放棄當次時間片,主動進入Runnable狀態(tài)
* setPriority():設(shè)置線程優(yōu)先級
* 優(yōu)先級越高的線程跛璧,理論上獲取cpu的次數(shù)就越多
* 設(shè)置線程優(yōu)先級一定要在線程啟動前設(shè)置严里!
*/
public class ThreadOtherMethod {
public static void main(String[] args){
Thread t1 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("你是誰啊追城?");
Thread.yield();
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我是修水管的刹碾。");
Thread.yield();
}
}
};
Thread t3 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我是打醬油的");
Thread.yield();
}
}
};
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
線程并發(fā)安全問題
多線程在訪問同一個數(shù)據(jù)時(寫操作),可能會引發(fā)不安全操作座柱。
- volatile關(guān)鍵字:用volatile修飾的變量迷帜,線程在每次讀取變量的時候,都會從主內(nèi)存中讀取最新的值色洞;每次給變量賦值時戏锹,都會把線程工作區(qū)變量的值同步到主內(nèi)存。volatile用來進行原子性操作火诸。當變量n=n+1锦针、n++ 等,volatile關(guān)鍵字將失效置蜀,只有當變量的值和自身上一個值無關(guān)時對該變量的操作才是原子級別的奈搜,volatile才有效。
/**
* 多線程并發(fā)安全問題
*
* synchronized關(guān)鍵字
* 線程安全鎖
* synchronized可以修飾方法也可以單獨作為語句塊存在
* synchronized的作用是限制多線程并發(fā)同時訪問該作用域
*/
public class ThreadSecure {
public static void main(String[] args){
/*main這個靜態(tài)方法中只能訪問靜態(tài)內(nèi)部類盯荤,所有Bank要用static修飾馋吗,
* 不用static修飾的話會提示
* 'com.example.ThreadSecure.this' cannot be referenced from a static context錯誤
* */
/*
* 創(chuàng)建銀行
* 創(chuàng)建兩個Person線程實例,并發(fā)從當前銀行對象中訪問數(shù)據(jù)
* */
Bank bank = new Bank();
/*下面這樣創(chuàng)建會提示
'com.example.ThreadSecure.Bank' is not an enclosing class錯誤
沒有靜態(tài)(static)修飾的類中類不能使用外部類進行.操作,
必須用實例來進行實例化類中類秋秤。
*/
//new Bank.Person();
//非靜態(tài)內(nèi)部類的創(chuàng)建和調(diào)用類實例的方法類似宏粤,類實例.new 非靜態(tài)內(nèi)部類名();
Bank.Person p1 = bank.new Person();
Bank.Person p2 = bank.new Person();
p1.start();
p2.start();
}
static class Bank{
int count = 10000;
//取錢方法
/*
* synchronized修飾方法后脚翘,方法就不是異步的了,而是同步的了商架,
* synchronized會為方法上鎖堰怨。
*
* synchronized同步塊
* synchronized (Object)
* {需要同步的代碼塊片段}
* Object指的是要上鎖的對象,
* 這里必須注意蛇摸!要確保所有線程看到的是同一個對象备图!否則起不到同步的效果!
* */
synchronized void getMoney(int money){
synchronized (this){
if (count==0){
throw new RuntimeException("余額為0");
}
/*try {
Thread.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
Thread.yield();
count-=money;
System.out.println("當前余額"+count);
}
}
/*線程里面代碼出錯且未捕獲異常赶袄,當前線程會被殺死揽涮,
但程序還是繼續(xù)運行,程序不會退出*/
class Person extends Thread{
@Override
public void run() {
while (true){
getMoney(100);
}
}
}
}
}
后臺線程
/**
* 后臺線程也稱為守護線程
* 特點:
* 當當前進程中所有前臺線程死亡后饿肺,后臺線程強制死亡蒋困。無論
* 是否還在運行。
*/
public class DaemonThread {
public static void main(String[] args){
Thread rose = new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("let me go!");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("AAAAaaaaaaa......噗通敬辣!");
}
};
Thread jack = new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("you jump!i jump雪标!");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
jack.setDaemon(true);//設(shè)置守護線程,必須在線程啟動之前設(shè)置
rose.start();
/*在執(zhí)行完成main方法的主線程執(zhí)行完畢死亡后溉跃,
接著rose線程執(zhí)行完畢相繼死亡村刨,這時前臺線程都死亡了,
守護線程jack強制死亡*/
jack.start();
}
}
- wait/notify方法
這兩個方法不是線程Thread中定義的方法撰茎,這兩個方法定義在Object中嵌牺。這兩個方法的作用是用于協(xié)調(diào)線程工作的。wait/notify方法必須保證在synchronized塊里面龄糊,而且等待哪個對象上就在哪個對象上上鎖逆粹,通知等待的線程運行和通知synchronized塊上的對象就是等待時上鎖的那個對象。
public class ThreadCoordinate {
public static void main(String[] args){
/*
* 創(chuàng)建銀行
* 創(chuàng)建兩個Person線程實例炫惩,并發(fā)從當前銀行對象中訪問數(shù)據(jù)
* */
Bank bank = new Bank();
Bank.Person p1 = bank.new Person();
Bank.Person p2 = bank.new Person();
p1.start();
p2.start();
/*p1和p2都在bank對象上等待了僻弹。進入了阻塞*/
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bank.count=10000;
System.out.println("銀行開門了");
//bank初始化完畢了
synchronized (bank){
bank.notifyAll();//通知在銀行上等待的所有線程可以工作了
}
}
static class Bank{
//count這個私有變量為什么可以在ThreadCoordinate這個類的main方法中訪問?
/*內(nèi)部類就相當于一個外部類的成員變量他嚷,所以可以直接訪問外部變量奢方,
外部類不能直接訪問內(nèi)部類變量,必須通過創(chuàng)建內(nèi)部類實例的方法訪問爸舒。
想不通的肯定是指內(nèi)部類的私有變量怎么可以被外部類訪問吧蟋字,
按常規(guī),私有變量m只能在InnerClass里被訪問扭勉,
但你要注意鹊奖,內(nèi)部類就相當于一個外部類的成員變量,舉個例子涂炎。
class Outer{
private int m;
private class Inner{
private int n;
private int k;
}
}
m和類Inner都是成員變量忠聚,他們之間是平等的设哗,唯一不同的
就是Inner它是包裝了幾個成員變量比如n,k,也就是說m n k是平等的两蟀,
區(qū)別在于訪問n k要通過Inner网梢,就是要建立Inner實例訪問n k
*/
private int count;
//取錢方法
void getMoney(int money){
synchronized (this){
if (count==0){
throw new RuntimeException("余額為0");
}
Thread.yield();
count-=money;
System.out.println("當前余額"+count);
}
}
class Person extends Thread{
@Override
public void run() {
System.out.println("準備取錢,等待銀行開門赂毯!");
synchronized (Bank.this){
try {
Bank.this.wait();//當前線程(Person)在銀行對象上等
} catch (InterruptedException e) {
e.printStackTrace();
}
}
while (true){
getMoney(100);
}
}
}
}
}
線程池
- 線程若想啟動需要調(diào)用start()方法战虏。這個方法要做很多操作。要和操作系統(tǒng)打交道党涕,注冊線程等工作烦感,等待線程調(diào)度。
- ExecutorService提供了管理終止線程池的方法膛堤。
- 線程池的創(chuàng)建都是工廠方法手趣。我們不要直接去new線程池,因為線程池的創(chuàng)建還要做很多的準備工作肥荔。
- Executors.newCachedThreadPool();
可根據(jù)任務(wù)需要動態(tài)創(chuàng)建線程绿渣,來執(zhí)行任務(wù)。若線程池中有空閑的線程將重用該線程來執(zhí)行任務(wù)燕耿。沒有空閑的則創(chuàng)建新線程來完成任務(wù)拆祈。理論上池子里可以放int最大值個線程贱枣。線程空閑時長超過1分鐘將會被回收屎蜓。一般用于處理執(zhí)行時間比較短的任務(wù)翼悴。(第二常用) - Executors.newFixedThreadPool();創(chuàng)建固定大小的線程池皆串。池中的線程是固定的呵晚。若所有線程處于飽和狀態(tài)矾芙,新任務(wù)將排隊等待假抄。(最常用)
- Executors.newScheduledThreadPool();
創(chuàng)建具有延遲效果的線程池隧出√ぶ荆可將待運行的任務(wù)延遲指定時長后再運行。 - Executors.newSingleThreadExecutor();
創(chuàng)建單線程的線程池胀瞪。池中僅有一個線程针余。所有未運行的任務(wù)排隊等待。
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 服務(wù)端
* 加入雙緩沖隊列凄诞,加快讀寫數(shù)據(jù)操作圆雁。
* 雙緩沖隊列可以規(guī)定隊列存儲元素的大小。
* 一旦隊列中的元素達到最大值帆谍,待插入的元素將等待伪朽。
* 等待時間是給定的。當給定時間到了元素還沒有機會被放入隊列
* 那么會拋出超時異常汛蝙。
*/
public class ServerDemo {
public static void main(String[] args){
System.out.println("服務(wù)器啟動中......");
ServerDemo demo = new ServerDemo();
demo.start();//連接服務(wù)器并通信
}
private ServerSocket socket;
private int port = 8088;
//線程池
private ExecutorService threadPool;
/*構(gòu)建ServerDemo對象時就打開服務(wù)器端口*/
public ServerDemo(){
try {
/*
* ServerSocket構(gòu)造對象要求我們傳入要打開的端口號
* ServerSocket對象在創(chuàng)建的時候就向操作系統(tǒng)申請打開
* 這個端口烈涮。
* */
socket = new ServerSocket(port);
//創(chuàng)建50個線程的固定大小的線程池
threadPool = Executors.newFixedThreadPool(50);
}catch (Exception e){
e.printStackTrace();
}
}
/*
* 開始服務(wù)
* 等待接收客戶端的請求并與其通信
* */
public void start(){
try {
/*通過調(diào)用ServerSocket的accept方法朴肺,使服務(wù)器開始等待
* 接收客戶端的連接。
* 該方法是一個阻塞方法坚洽,監(jiān)聽8088端口是否有客戶端連接戈稿。
* 直到有客戶端與其連接,否則該方法不會結(jié)束讶舰。
* */
while (true){
System.out.println("等待客戶端連接......");
Socket s = socket.accept();
System.out.println("一個客戶端連接了鞍盗,分配線程去接待它");
/*當一個客戶端連接了,就啟動一個線程去接待它*/
//Thread clientThread = new Thread(new Handler(s));
//clientThread.start();
/*
* 將線程體(并發(fā)的任務(wù))交給線程池
* 線程池會自動將該任務(wù)分配給一個空閑線程去執(zhí)行绘雁。
* */
threadPool.execute(new Handler(s));
}
} catch (IOException e) {
e.printStackTrace();
}
}
/*定義線程體橡疼。該線程的作用是與連接到服務(wù)器端的客戶端
進行交互操作*/
class Handler implements Runnable{
//當前線程要進行通信的客戶端Socket
private Socket socket;
//通過構(gòu)造方法將客戶端的Socket傳入
public Handler(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
PrintWriter writer = new PrintWriter(out);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
//先聽客戶端發(fā)送的信息
String info = reader.readLine();//這里同樣會阻塞
System.out.println(info);
//發(fā)送信息給客戶端
writer.println("你好!客戶端");
writer.flush();
info = reader.readLine();
System.out.println(info);
writer.println("再見庐舟!客戶端");
writer.flush();
socket.close();//關(guān)閉與客戶端的連接
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/**
* MINA框架 開源的socket框架欣除。很多游戲服務(wù)器的通信框架全用它
*
* 客戶端
*/
public class ClientDemo {
public static void main(String[] args){
ClientDemo demo = new ClientDemo();
demo.send();//連接服務(wù)器并通信
}
private Socket socket;
/*
* 建立連接并向服務(wù)器發(fā)送信息
* 步驟:
* 1:通過服務(wù)器的地址及端口與服務(wù)器連接
* 創(chuàng)建Socket時需要以上兩個數(shù)據(jù)
* 2:連接成功后可以通過Socket獲取輸入流和輸出流
* 使用輸入流接收服務(wù)端發(fā)送過來的信息
* 使用輸出流信息發(fā)送給服務(wù)端
* 3:關(guān)閉連接
* */
public void send(){
try {
System.out.println("開始連接服務(wù)器");
/*1、連接服務(wù)器
* 一旦Socket被實例化挪略,那么它就開始通過給定的地址和
* 端口號去嘗試與服務(wù)器進行連接历帚。
* 這里的地址“l(fā)ocalhost”是服務(wù)器的地址
* 8088端口是服務(wù)器對外的端口。
* 我們自身的端口是系統(tǒng)分配的杠娱,我們無需知道挽牢。
* */
socket = new Socket("localhost",8088);
/**
* 和服務(wù)器通信(讀寫數(shù)據(jù))
* 使用socket獲取輸入和輸出流
*/
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
/*將輸出流變成處理字符串的緩沖字符輸出流*/
PrintWriter writer = new PrintWriter(out);
writer.println("你好!服務(wù)器摊求!");
/*
* 注意禽拔,寫到輸出流的緩沖區(qū)里了,并沒有真的發(fā)給服務(wù)器
* 想真的發(fā)送就要做真實的寫操作室叉,清空緩沖區(qū)
* */
writer.flush();
//將輸入流轉(zhuǎn)換為緩沖字符輸入流
BufferedReader reader = new BufferedReader(
new InputStreamReader(in)
);
/*讀取服務(wù)器發(fā)送過來的信息*/
String info = reader.readLine();//讀取服務(wù)器信息會阻塞
System.out.println(info);
writer.println("再見睹栖!服務(wù)器!");
writer.flush();
info = reader.readLine();
System.out.println(info);
}catch (Exception e){
e.printStackTrace();
}
}
}
- 雙緩沖隊列:BlockingQeque:解決了讀寫數(shù)據(jù)阻塞問題茧痕。但是同時寫或讀還是同步的野来。
//雙緩沖隊列
private BlockingQueue<String> msgQueue;
/*
* 創(chuàng)建規(guī)定大小的雙緩沖隊列
* LinkedBlockingQueue是一個可以不指定隊列大小的
* 雙緩沖隊列。若指定大小踪旷,當達到峰值后曼氛,待入隊的將
* 等待。理論上最大值為int最大值
* */
msgQueue = new LinkedBlockingQueue<String>(10000);
/*
* 創(chuàng)建定時器令野,周期性的將隊列中的數(shù)據(jù)寫入文件
* */
Timer timer =new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
PrintWriter writer =
new PrintWriter(new FileWriter("log.txt",true));
//從隊列中獲取所有元素舀患,做寫出操作
String msg = null;
for (int i = 0; i < msgQueue.size(); i++) {
/*
* 參數(shù) 0:時間量
* TimeUnit.MILLISECONDS:時間單位
* */
msg = msgQueue.poll(0, TimeUnit.MILLISECONDS);
if (msg==null){
break;
}
writer.println(msg);//通過輸出流寫出數(shù)據(jù)
}
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
},0,500);
String msg = null;
while (true){
//循環(huán)讀取客戶端發(fā)送過來的信息
msg = reader.readLine();
if (info!=null){
//插入隊列成功返回true,失敗返回false气破。
//該方法會阻塞線程聊浅,若中斷會報錯!
boolean b = msgQueue.offer(msg,5,TimeUnit.SECONDS);
}
}