標籤:死迴圈 tac 決定 優先順序 obj 設定 script 隊列 while
JAVA並行程式基礎一、有關線程你必須知道的事進程與線程
- 在等待面向線程設計的電腦結構中,進程是線程的容器。我們都知道,程式是對於指令、資料及其組織形式的描述,而進程是程式的實體。
- 線程是輕量級的進程,是程式執行的最小單位。(PS:使用多線程去進行並發程式的設計,是因為線程間的調度和切換成本遠小於進程)
-
線程的狀態(Thread的State類):
- NEW–剛剛建立的線程,需要調用start()方法來執行線程;
- RUNNABLE–線程處於執行狀態;
- BLOCKED–線程遇到synchronized同步塊,會暫停執行直到獲得請求的鎖;
- WAITING–無限制時間的等待;
- TIMED_WAITING–有限制時間的等待;
- TERMINATED–執行完畢,表示結束。
二、線程的基本操作建立線程
程式碼範例:start()方法會建立線程並調用run()方法!
Thread t = new Thread();t.start();
注意以下代碼:這段代碼也可以編譯執行,但不能建立一個線程,而是在當前線程中調用run()方法。(PS:就是作為一個普通方法來調用)
Thread t = new Thread();t.run();
不能用run()來開啟線程,它只會在當前線程中串列執行run()方法。
另外,Thread類有一個非常重要的構造方法:
public Thread(Runnable target)
這個構造方法在start()調用時,新線程會執行Runnable.run()方法。實際上,預設的Thread.run()也是這樣子做的。
public class MyThread implements Runnable { public static void main(String[] args) { Thread t = new Thread(new MyThread()); t.start(); } @Override public void run() { System.out.println("開啟新線程執行run()方法"); }}
- 繼承Thread的方式定義線程後,就不能在繼承其他的類了,導致程式的可擴充性大大降低。而且**重載**run()方法也只是和普通方法一樣,不會被JVM主動調用。
- 使用實現Runnable介面並傳入執行個體給Thread,可以避免重載Thread.run(),單純使用介面來定義Thread。
終止線程
JAVA提供了一個stop()方法來關閉線程,不過這是一個被標註為廢棄的方法。原因是stop()方法太過暴力,強行將執行到一半的線程關閉可能導致一些資料不一致的問題。
一幫情況下,不要使用 stop() 方法!
解決方案:定義一個標記變數stopme,用於指示線程是否需要退出。
線程中斷
在JAVA中,為了完善安全執行緒退出,有一種重要的線程協作機制————線程中斷。(注意:線程中斷會給線程發送一個通知,但線程接到通知後如何處理,則由目標線程自行決定)
public void Thread.interrupt() //中斷線程 public boolean Thread.isInterrupted() //判斷線程是否被中斷 public static boolean Thread.interrupted() //判斷線程是否被中斷,並清除當前中斷狀態
Thread.interrupt()方法中斷,線程不會立刻停下來,比如死迴圈體。可以通過isInterrupted()方法來判斷是否跳出迴圈體。
線程休眠
-
Thread.sleep()
讓當前線程休眠若干時間,其簽名如下:
public static native void sleep(long millis) throws InterruptedException
- InterruptedException不是運行時異常,當線程處於休眠狀態時,如果被中斷就會拋出這個異常。
- sleep()方法由於中斷拋出異常時,會清除中斷標記。所以,在異常處理中需要再次設定中斷標記!
-
Thread.wait()與notify()
這兩個方法是配套的,一個線程調用obj.wait()處於等待狀態,需要其他線程調用obj.notify()來喚醒。obj對象就是多個線程間有效通訊手段。
- Object.wait() 必須包含在 s’y’nchronzied 語句中,無論是 wait() 還是 notify() 都需要先獲得目標對象的一個監視器。(PS:這兩個方法都是執行後立刻釋放監視器,防止其他等待對象線程因為該線程休眠而全部無法正常執行)
- Object.wait() 同樣會拋出
InterruptedException異常。
- Object.notifyAll() 方法會喚醒等待隊列中所有等待的線程。
-
suspengd()與resume()
前者是線程掛起,後者是繼續執行,這是一對互相配合的方法。這兩個也是已經被廢棄的方法,瞭解一下就行了。
- suspend() 會導致休眠線程的所有資源都不會被釋放,直到執行 resume() 方法。(如果兩個方法,後者執行於前者之前,則線程很難有機會被繼續執行)
- 對於被掛起的線程,其狀態是
Runnable,這會嚴重影響到我們對於系統目前狀態的判斷。(不可饒恕)
-
join()與yield()
-
join() 方法
其簽名如下:
public final void join() throws InterruptedException;
public final void join(long millis) throws InterruptedException;
第一個方法表示無限等待;第二個方法表示一定時間的等待。join()方法會阻塞當前線程,直到目標線程結束,或者到達阻塞時間。
同時,join()方法的本質是讓在當前線程對象執行個體上調用線程的wait()方法。
-
yield() 方法
其簽名如下:
public static native void yield();
這個靜態方法會使當前線程讓出CPU。(PS:但還是會進行CPU資源的搶奪)
當某個線程不是那麼重要,或者優先順序別較低,可以在適當時候調用 Thread.yield(),給予其他重要線程更多工作機會。
關於線程操作的補充
- sleep() 與 wait() 的區別
Object.wait() 和 Thread.sleep() 都可以讓線程等待若干時間。除了** wait() 可以被喚醒外,另外一個主要區別:**wait() 方法會釋放目標對象的鎖,而 sleep() 方法不會釋放任何資源。
三、分門別類的管理:線程組
簡單建立一個線程組(代碼來源於《實戰Java高並發程式設計》書中)
public class MyThread implements Runnable { public static void main(String[] args) { //建立一個叫"PrintGroup"的線程組 ThreadGroup tg = new ThreadGroup("PrintGroup"); Thread t1 = new Thread(tg, new MyThread(), "T1"); //加入線程組 Thread t2 = new Thread(tg, new MyThread(), "T2"); t1.start(); t2.start(); //由於線程是動態,activeCount()獲得活動線程總數的估算值 System.out.println("活動線程總數 = " + tg.activeCount()); //列印出線程組的線程資訊 tg.list(); //注意這個方法與Thread.stop()方法遇到問題一樣 tg.stop(); } @Override public void run() { String groupAndName = Thread.currentThread().getThreadGroup().getName() + "---" + Thread.currentThread().getName(); while (true) { System.out.println("I am " + groupAndName); try { Thread.sleep(3000); } catch (InterruptedException e){ e.printStackTrace(); } } }}
四、駐守背景守護線程
守護線程,是系統的守護者,在後台完成系統性的服務,比如記憶體回收線程、JIT線程等。當一個Java應用內只有守護線程時,Java虛擬機器就會自然退出。
將線程設定為守護線程:
Thread t = new Thread(new MyThread()); t.setDaemon(true); t.start();
注意:設定守護線程必須線上程start()之前設定,否則會得到一個IllegalThreadStateException,但程式和線程依然可以正常執行,只是線程被當作使用者線程而已。
五、線程優先順序
Java中線程可以有自己的優先順序。由於線程的優先順序調度和底層作業系統有密切關係,在各個平台表現不一,無法精準控制。
在Java中,使用 1 到 10 表示線程優先順序。一般可以使用三個內建的靜態標量表示:
public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10;
PS:數字越大,優先順序越高(一般情況)。
六、synchronized 關鍵字
synchronized 關鍵字用法簡單記錄:
- 指定加鎖對象:對給定對象加鎖,進入同步代碼前要獲得給定對象的鎖。
- 直接作用於執行個體對象:相當於對當前執行個體加鎖,進入同步代碼前要獲得當前執行個體的鎖。
- 直接作用於靜態方法:相當於對當前類加鎖,進入同步代碼前要獲得當前類的鎖。
synchronized 可以保證線程間的原子性、可見度和有序性(被 synchronized 限制的多個線程是串列執行的)。
七、部分隱秘的錯誤記錄
- 執行(synchronized)同步代碼的兩個線程指向了不同的Runnable執行個體,即兩個線程使用了兩個不同的鎖。解決方案:將同步代碼修改為靜態(static)方法。
- 使用線程不安全的容器,如 ArrayList、HashMap 等。解決方案:改用安全執行緒的容器,如 Vector、ConcurrentHashMap。
- 不變對象加鎖,導致對於臨界區代碼控制出現問題。
- 不變對象:對象一旦被建立,就不能修改。
(如,Integer i; i++的本質是建立一個新的Integer對象,並將引用賦值給i,同時這裡涉及到Integer引用的特性)
八、參考資料
- 《實戰Java高並發程式設計》(葛一鳴 郭超 著)
JAVA並行程式基礎