線程
1.程序如果只有后臺進程而沒有一個前臺進程,那么整個java程序就會結(jié)束。用setDaemon(true)可以把線程設(shè)為后臺線程,普通創(chuàng)建的線程都是前臺線程。
2.一個線程類繼承Thread類,這個子類實現(xiàn)run方法,run方法處理的事物的處理。啟動線程用start方法。[thread類本身的run方法是空的,所以需要子類去實現(xiàn),start方法也是thread類里繼承過來的,由于java的多態(tài)性,所以start方法會啟動子類的run方法]
3.join()方法可以合并線程,如果不帶參數(shù)表示永久合并,如果帶參數(shù)表示線程合并多少豪秒后又分開執(zhí)行
class ThreadDemo
{
public static void main(String [] args)
{
Thread tt = new TestThread();
//tt.setDaemon(true);把線程tt置為后臺線程
tt.start();//這個方法會自動去調(diào)用tt的run()
int index = 0;
while(true)
{
if(index++ == 100)//執(zhí)行100次后合并線程
try{tt.join(10000);//把tt線程合并到主線程10秒,如果這里是沒有參數(shù),那么就永遠執(zhí)行run這 //個線程,因為這個線程是個死循環(huán),下面的語句就執(zhí)行不到了,哈哈} catch(Exception e){}
System.out.println("main()"+Thread.currentThread().gerName()//得到當前執(zhí)行線程的名字);
}
}
}
class TestThread extends Thread
{
public void run()
{
while(true)
{
System.out.println("run()"+Thread.currentThread().gerName()//得到當前執(zhí)行線程的名字);
}
}
}
以上說了如何用繼承Thread類來創(chuàng)建線程,還有一種方法是類來實現(xiàn)Runnable接口來創(chuàng)建線程
class TestThread extends Thread 這個就要改成 class TestThread implements Runnable
而main中的Thread tt = new TestThread(); 就要改成 Thread tt = new Thread(new TestThread());這里接受的類型是Runnable型的
那么就出現(xiàn)了一個問題,既然兩種方法都可以創(chuàng)建線程,那么有什么區(qū)別呢?我們來看一個鐵路模擬售票系統(tǒng):
有100張票,我們啟動4個線程來賣他們:
class ThreadDemo
{
public static void main(String [] args)
{
/* 如果用這種寫法,你會發(fā)現(xiàn)實際是啟動了4個線程,然后4個線程都有各自的100張票,各買各的,所以這樣是錯的。
new TestThread();
new TestThread();
new TestThread();
new TestThread(); */
/*如果是這種寫法,從輸出結(jié)果中我們發(fā)現(xiàn)無論start了多少次,實際還是一個線程,所以這樣也是錯的。
TestThread tt = new TestThread();
tt.start();
tt.start();
tt.start();
tt.start();
*/
}
}
class TestThread extends Thread
{
int tickets = 100;
public void run()
{
while(true)
{
if(tickets>0)
System.out.println(Thread.currentThread().gerName()+"is saling ticket "+tickets--);
}
}
}
這種情況下我們就使用runnable了。請看:
class ThreadDemo
{
public static void main(String [] args)
{
TestThread tt = new TestThread();
new Thread(tt).start();//四個線程調(diào)用同一個線程對象
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
}
}
class TestThread implements Runnable
{
int tickets = 100;
public void run()
{
while(true)
{
if(tickets>0)
System.out.println(Thread.currentThread().gerName()+"is saling ticket "+tickets--);
}
}
}
總結(jié):使用runnable接口創(chuàng)建多線程比繼承thread要靈活。他適合多個相同的程序代碼的線程去處理同一資源的情況。
多線程的應(yīng)用:
1.網(wǎng)絡(luò)聊天工具開發(fā)
一個線程負責發(fā)消息,一個負責收消息。兩者互不干擾,如果是單線程,那么就可能前面在等待,而后面就接收不了消息。
2.大量數(shù)據(jù)庫記錄的復(fù)制。
如果一個復(fù)制要花3天時間,那么當你中途覺得沒有必要,要停止的時候,你發(fā)現(xiàn)你不能讓他停下來。而多線程的情況,一個
現(xiàn)成負責拷貝,是個無限循環(huán)
while(bStop)
{get data
copy data}
另一個現(xiàn)成監(jiān)督是否有鍵盤按下,也就是把原本為true的變量bstop置為false。 bStop = false;當另一線程監(jiān)視到變量 變化時會停止程序
3.www服務(wù)器為每一個來訪者創(chuàng)建一個線程
線程安全問題:
我們在上面做的鐵路模擬獸票系統(tǒng)有個問題:線程安全問題!
假如當tickets==5的時,系統(tǒng)正好正好要執(zhí)行tickets--,而還美意執(zhí)行這個的時候,cpu被切換到另一個線程。那么tickets仍然是5,所以很坑5這張票打印了2次,另一種情況:當票為1的時候系統(tǒng)剛好要打印出1,而這個時候cpu馬上切換到了其他線程,它發(fā)現(xiàn)票還是1,所以通過了判斷大于0,所以打印出了票1。而這個時候票--為0,cpu切換到了剛才的線程,由于剛才的線程在執(zhí)行打印,所以把票0給打印出來,這些都造成了線程的安全問題
所以我們要把if語句塊作為一個原子,放到synchronized()里面。synchronized()里面必須有參數(shù),比如
String str = new String("");
然后把if語句塊放到synchronized(str){ if... }里面 這樣就實現(xiàn)了同步,避免了線程的不安全。
另外,我們?nèi)绻M粋€方法是線程安全的,那么我們可以直接在方法名前寫上synchronized關(guān)鍵字,比如:
public synchronized void sale(){......} //實際上這種情況下synchronized使用的同步對象是this
注意:str的定義必須放到run方法之外,這個對象應(yīng)該是全局的,這樣才能作為鎖旗標!(系統(tǒng)會自動對這個對象進行標識)
線程執(zhí)行的內(nèi)部機制:
new Thread(tt).start();
執(zhí)行start并沒有馬上去執(zhí)行線程的run方法,而是再向下繼續(xù)執(zhí)行一會,因為cpu還沒有這么快就切換到線程上,要想讓他馬上切換,可以在start后寫上:try(Thread.sleep(1);//哪怕是一秒,他也會切換)catch(Exception e){}
如果把代碼塊的同步用synchronized(this),那么代碼塊會和有synchronized的方法同步!
1:31 為止 5
類似下面的代碼在多線程調(diào)用它的時候也要注意線程安全問題:
public void push(char c)
{
data[index] = c;
index++;
}
另外有一場景[線程間通信問題]:生產(chǎn)者不停地產(chǎn)生一組數(shù)據(jù),消費者不停地去取數(shù)據(jù),數(shù)據(jù)都在緩存中,
class Producer implements Runnable
{
Q q;
public Producer(Q q)
{
this.q = q;
}
public void run()
{
int i = 0;
while(true)
{
if(i==0)
{q.name = "zhangsan";
try{Thread.sleep(1);}catch(Exception e){}//用sleep來模擬cpu切換的現(xiàn)象
q.sex = "male";
}
else
{q.name = "lisi";
q.sex = "female";
}
i = (i+1)%2; //使i在0和1之間切換
}
}
}
class Customer implements Runnable
{
Q q;
public Customer(Q q)
{
this.q = q;
}
public void run()
{
while(true)
{
System.out.println(q.name);
System.out.println(":"+q.sex);
}
}
}
class Q
{
String name = "unknow";
String sex = "unknow";
}
//run class
class ThreadCommunation
{
public static void main(String [] args)
{
Q q = new Q();
new Thread(new Prodecer(q)).start();
new Thread(new Customer(q)).start();
}
}
運行后我們會發(fā)現(xiàn)名字和性別并沒有對應(yīng)起來,這是因為線程沒有同步的問題,要解決這個問題,只需要在兩個類的while里
分別加上synchronized(q){}語句塊,因為他們都使用同一個對象q,所以用q作為同步監(jiān)視器
上面的代碼還不是很完善,因為有當消費者取得同步監(jiān)視器卻發(fā)現(xiàn)緩沖區(qū)根本沒有數(shù)據(jù)的情況,那怎么辦?或者當生產(chǎn)者在
取得同步監(jiān)視器開始生產(chǎn)數(shù)據(jù)的時候又取得了同步監(jiān)視器開始放數(shù)據(jù),這樣就會把原先的數(shù)據(jù)覆蓋!我們使用了wait和notify
對程序修改如下:
class Producer implements Runnable
{
Q q;
public Producer(Q q)
{
this.q = q;
}
public void run()
{
int i = 0;
while(true)
{
synchronized(q)
{
if(q.bFull)//如果緩沖區(qū)是滿的 有數(shù)據(jù)的 那么生產(chǎn)者線程應(yīng)該wait等待消費者來取數(shù)據(jù)
try{q.wait();}catch(Exception e){}
if(i==0)
{q.name = "zhangsan";
try{Thread.sleep(1);}catch(Exception e){}//用sleep來模擬cpu切換的現(xiàn)象
q.sex = "male";
}
else
{q.name = "lisi";
q.sex = "female";
}
q.bFull = true;
q.notify();//通知消費者有數(shù)據(jù)要他來取數(shù)據(jù)
}
i = (i+1)%2; //使i在0和1之間切換
}
}
}
class Customer implements Runnable
{
Q q;
public Customer(Q q)
{
this.q = q;
}
public void run()
{
while(true)
{
synchronized(q)
{
if(!q.bFull) //緩沖區(qū)為空的時候交出同步監(jiān)視器開始等待
try{q.wait();}catch(Exception e){}
System.out.println(q.name);
System.out.println(":"+q.sex);
q.bFull = false; //取走數(shù)據(jù)后緩沖區(qū)為空
q.notify(); //通知生產(chǎn)者線程開始執(zhí)行,這個notify與Producer中的wait對應(yīng)
}
}
}
}
class Q
{
String name = "unknow";
String sex = "unknow";
boolean bFull = false;
}
//run class
class ThreadCommunation
{
public static void main(String [] args)
{
Q q = new Q();
new Thread(new Prodecer(q)).start();
new Thread(new Customer(q)).start();
}
}
注意:wait和notify方法必須是synchronized的監(jiān)視器對象的方法,既如果有
synchronized(q),那么就應(yīng)該q.wait();q.notify(); 如果不寫對象,他會默認是this對象
而有可能導(dǎo)致運行時錯誤[編譯沒有錯誤]
任何對象都有wait,notify,notifyAll方法,
wait:告訴當前線程放棄監(jiān)視器并進入睡眠狀態(tài)直到其他線程進入同一監(jiān)視器并調(diào)用notify為止;
notify:喚醒同一對象監(jiān)視器中調(diào)用wait的第一個線程。
用于類似飯館有一個空位后通知所有等候就餐的顧客中的第一位可以入座的情況。
notifyAll:喚醒同一對象監(jiān)視器中調(diào)用wait的所有線程。具有最高優(yōu)先級的線程首先被喚醒并執(zhí)行。
用于類似某個不定期的培訓(xùn)班終于招生滿額后,通知所有的學(xué)員都來上課的情況。
實際上上面的代碼有些亂,這是因為程序的結(jié)構(gòu)設(shè)計不夠合理,應(yīng)該把數(shù)據(jù)的放和取放到q類中,然后在
這兩個方法名前加上關(guān)鍵字synchronized,類似
public synchronized void put(String name,String sex)
{
if(bFull)
try{wait();}catch(Exception e){}
this.name = name;
try{Thread.sleep(1);}catch(Exception e){}
this.sex = sex;
bFull = true;
notify();
}
我們給別人提供的類是線程安全的,類似上面的代碼。別人在使用這個類的時候就不需要考慮線程安全問題,反之,就需要在
外面處理線程安全問題
控制線程的生命周期:
class ThreadLife
{
public static void main(String [] args)
{
ThreadTest tt = new ThreadTest();
new Thread(tt).start();
for(int i=0;i<100;i++)
{
if(i==50)
tt.stopMe();
System.out.println("main() is runing");
}
}
}
class ThreadTest implements Runnable
{
private boolean bStop = false;
public void stopMe()
{
bStop = true;
}
public void run()
{
while(!bStop)
{
System.out.println(Thread.currentThread().getName()+"is running");
}
}
}