標籤:java 多線程 java
一直都很想寫關於多線程的東西,以來可以鞏固鞏固自己的知識,而來可以看看自己的掌握的水平,因為一直都覺得這方面挺有意思的好了。廢話不多說,入正題。
java多線程,我們首先想多的是什麼。進程,Thread,Runnable,start,run...
那我們就先從他們入手了。為什麼會想到進程呢。以為一直都是多線程多進程的說。那他們有什麼區別。
進程:進程是程式的運行和作業系統分配資源的最基本的獨立單位。每個進程在沒有特殊的處理下,是各自獨立的。
線程呢:線程不能獨立存在,線程必須依附於進程,線程共用進程的資源,從某種意義上來說,使用線程對系統開銷更小,效率應該高點。
在java中,主要兩種方式能夠實現多線程的編程。一種是繼承Thread類,一種是實現Runnable介面。要說兩者的區別:我覺得無非是
1.Runnable更適合線程間的資源共用,也更推薦使用Runnable。
2.Runnable是介面所以可以避免Thread單繼承的缺陷。
好了,這隻是嘴上說說而已,還是來看看具體代碼吧。
我們來分別實現一個經典的視窗買票程式:
繼承Thread:
<span style="font-size:14px;">class SalerThread extends Thread{ private int sum = 5; public void sale(){ System.out.println(getName() + "號視窗正在售票,還剩" + (--sum) + "張票."); } @Override public void run(){ while (sum > 0) { sale(); } }}public class TestThread { public static void main(String[] args) { SalerThread thread = new SalerThread(); SalerThread thread1 = new SalerThread(); SalerThread thread2 = new SalerThread(); thread.setName("1"); thread1.setName("2"); thread2.setName("3"); thread.start(); thread1.start(); thread2.start();}}我們可以看到結果,很明顯不符合我們的實際情況,但是但我們換成實現Runnable呢!class SalerRunnale implements Runnable{ private int sum = 5; public void sale(){ System.out.println(Thread.currentThread().getName() + "號視窗正在售票,還剩" + (--sum) + "張票."); } @Override public void run() { while (sum > 0) { sale(); } }}public class TestThread { public static void main(String[] args) { SalerRunnale runnale = new SalerRunnale(); Thread thread = new Thread(runnale, "1"); Thread thread1 = new Thread(runnale, "1"); Thread thread2 = new Thread(runnale, "1"); thread.start(); thread1.start(); thread2.start(); }}</span>
兩個線程主體基本一樣,就是在調用Thread中的方法時,我們需要多寫一步Thread.currentThread.
也許你想問了,為什麼我們是調用start()呢,我們當然也可以調用run()啦,我保證,編譯器不會報錯,而且有結果,但是,這個結果是按照我們調用所謂的線程的順序輸出的。
也許你會想,哼,肯定只是偶然,但當我們多次調用之後,結果依然不變。好了,其實,在直接調用run()方法時,jvm只是把它當做一個普通方法進行調用,並不會給他重新分配一個線程。所以一定要記住,在寫新線程的時候,調用的是它的start()方法,而不是我們重寫的run()方法。
好了,現在聊一聊多線程的一些狀態:
線程可以有6種狀態:
*New(新建立)
*Runnable(可運行)
*Blocked(被阻塞)
*Waiting(阻塞)
*Timed waiting(計時等待)
*Terminated(被終止)
摘自:http://my.oschina.net/mingdongcheng/blog/139263)
線程之間是有自己的優先順序的,相信這個詞你應該很熟悉。預設情況下,子線程是繼承父線程的優先順序,當然,你也可以通過調用setPriority方法來設定優先權。線程的優先順序應該在MIN_PRIORITY(Thread中定義為1)到MAX_PRIORITY(Thread中定義為10)。
有了jvm預設是先調用優先順序高的線程,預設的是NORM_PRIORITY(Thread中定義5),當我們線上程中調用yield()時,會使當前線程處於一種讓步的狀態,即先讓不低於自己的程式先運行,假如沒有比自己高的呢,依舊自己運行。當然這不是絕對的,他依靠作業系統的支援。
有一種線程叫做守護線程。顧名思義,他是需要被守護者的,假如只有他自己則沒有意義。所以,他不會獨立存在。調用他們非常簡單。只需要在start()之前調用setDeamon(true)即可。
但是,他依舊有缺陷,守護進程不應該去訪問固有資源,如檔案、資料庫,因為他會在任何時候甚至在一個操作的中間發生中斷。
我們在一個線程運行到一半想終止他怎麼辦。java一開始提供了stop方法,但是因為他很不安全,所以被棄用了。不僅如此,java還提供線程掛起的方法suspend和喚醒的方法resume。不過suspend很容易導致死結的情況,所以也已經被拋棄了。
但是假如我們需要暫停一個線程怎麼辦。
我們可以設定一個boolean值。
如private boolean running;
需要暫停時候
public synchronized void stop(){
running = false;
}
而我們在run方法中假如running的條件判斷
while (running){
//your code...
}
如此,我們便實現了自己的暫停方法。當然才學到這的你可能會問為什麼要加synchronized呢,他是什麼意思呢。這個問題我們稍後會介紹,當然,你也可以看看我之前寫的那一篇部落格---超連結---。
好了,到這,我們還需要說一下線程的中斷。線程在執行完run方法最後一條語句後,或者有未捕獲的異常時,線程會終止。當然,我們可以調用interrupt方法來終止線程。每個線程都有表示中斷狀態的boolean值。每個線程都應該不時檢查這個標誌位,當然,假如在調用它之前,線程已經調用了wait,sleep或者join,他的interrupt status將會被清除,並且會拋出一個InterruptedException.異常。
Thread還有一個join方法,在一個線程A中調用另一個線程B,線程B調用join,那麼線程A將等待線程B執行完畢。或者調用join的重載方法,設定時間表示之多等待多久。
<span style="font-size:14px;"><span style="font-size:12px;">class RunnableA implements Runnable{ @Override public void run() { for (int i = 0; i < 5; i++){ System.out.println("threadA :" + i); } }}public class TestForJoin { public static void main(String[] args) throws InterruptedException { RunnableA runnableA = new RunnableA(); Thread threadA = new Thread(runnableA); threadA.start();// threadA.join(); for (int i = 0; i < 5; i++){ System.out.println(Thread.currentThread().getName() + " :" + i); } }}</span></span>當我們注釋threadA.join();時,結果:
當我們取消注釋:
假如是獨立的兩個線程,一個線程調用join,對另外一個線程是不起作用的。
<span style="font-size:14px;"><span style="font-size:12px;">public class TestForJoin { public static void main(String[] args) throws InterruptedException { RunnableA runnableA = new RunnableA(); Thread threadA = new Thread(runnableA, "ThreadA"); Thread ThreadB = new Thread(runnableA, "ThreadB"); threadA.start(); threadB.start(); threadA.join(); for (int i = 0; i < 5; i++){ System.out.println(Thread.currentThread().getName() + " :" + i); } }}</span></span>因為在一個線程A中調用另一個線程B,B線程調用join,這隻會阻塞A線程,假如同時存線上程C,C依舊可以和B搶佔CPU.
我們說過線程之間的資源出自身的一些變數方法,其餘都共用,所以,多線程必定會產生多個線程同時修改一塊記憶體塊的問題。我們該如何避免呢。
最簡單也是最有效方法當然是不使用多線程啦。但是當我們必須使用多線程的時候我們該怎麼辦。
我們先來做一個小程式,多線程按順序列印1-9,要求先列印1-3,在列印4-6,最後全部輸出。
<span style="font-size:14px;"><span style="font-size:12px;">public class ThreadPrint { static Lock lock = new ReentrantLock(); static Condition reachThree = lock.newCondition(); static Condition reachSix = lock.newCondition(); public static void main(String[] args) { final int[] integer = {0}; Thread threadA = new Thread(new Runnable() { @Override public void run() { lock.lock(); try { for (int i = 1; i <= 3; i++) { System.out.println(i); } integer[0] = 3; reachThree.signal(); } finally { lock.unlock(); } } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { lock.lock(); try { while (integer[0] != 3) { reachThree.await(); } for (int i = 4; i <= 6; i++) { System.out.println(i); } integer[0] = 6; reachSix.signal(); } catch (InterruptedException e) { } finally { lock.unlock(); } } }); Thread threadC = new Thread(new Runnable() { @Override public void run() { lock.lock(); try { while (integer[0] != 6) { reachSix.await(); } for (int i = 6; i <= 9; i++) { System.out.println(i); } integer[0] = 9; } catch (InterruptedException e) { } finally { lock.unlock(); } } }); threadA.start(); threadB.start(); threadC.start(); }}</span></span>
Lock是concurrent中給我們提供的一種鎖機制,當一個線程進入一個類時,便給它加上鎖,當還有其他的線程在之前線程還未退出,請求範文該類時,就會被阻塞,直至第一個進入的線程退出並解除鎖。condition是一個條件,一個鎖可以有一個或多個條件。當線程因為一個條件被阻塞時,會自動釋放它的鎖,一邊其他線程進入並喚醒它。我們調用的鎖ReentrantLock是一種可重新進入的鎖,但一個線程進入一個類,調用一個含鎖的方法時,它的計數器會加1,當釋放一個鎖時便減一,每次有其他線程進入該類會檢查計數器是否為0.我們還可以在調用建構函式時將它設定成公平鎖new ReentrantLock(true),即等待時間越長越有可能獲得該鎖,但是它的效率相比不公平鎖會低很多,所以不是必須使用它,我們總是使用預設的不公平鎖。
當然還有一種ReentrantReadWriteLock,當線程頻繁對資料進行讀操作時,他是一個很好的選擇,它有讀鎖和寫鎖,它允許多個線程進行讀取,指定數量的線程進行寫操作。所以效率高。每次進行讀操作時利用ReentrantReadWriteLock.ReadLock,寫操作便用ReentrantReadWriteLock。
當然java還提供一種更為簡單的機制synchronized,可以設定同步塊和同步方法,具體可以參考我的另一篇博文:初探java 對象中wait(),notify(),notifyAll() 和線程中的synchronized。需要多說的是,synchronized調用的實際上是一個內部鎖,它只含有一個條件。
你會想那什麼時候用synchronized什麼時候用Lock呢。
1.其實最好是兩個都不用,因為在我們的concurrent包中,有同步隊列,他會自動幫我們處理資料的同步問題。
2.如果synchronized能夠符合我們要求時,盡量使用它。
3.當我們需要用到Lock/Condition的特殊功能時,才考慮使用它。
java 多線程初探