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