所有線程都隸屬於一個線程組。那可以是一個默認線程組,亦可是一個創建線程時明確指定的組。在創建之初,線程被限制到一個組裡,而且不能改變到一個不同的組。每個應用都至少有一個線程從屬於系統線程組。若創建多個線程而不指定一個組,它們就會自動歸屬於系統線程組。
線程組也必須從屬於其他線程組。必須在構建器裡指定新線程組從屬於哪個線程組。若在創建一個線程組的時候沒有指定它的歸屬,則同樣會自動成為系統線程組的一名屬下。因此,一個應用程序中的所有線程組最終都會將系統線程組作為自己的“父”。
之所以要提出“線程組”的概念,很難從字面上找到原因。這多少為我們討論的主題帶來了一些混亂。一般地說,我們認為是由於“安全”或者“保密”方面的理由才使用線程組的。根據Arnold和Gosling的說法:“線程組中的線程可以修改組內的其他線程,包括那些位於分層結構最深處的。一個線程不能修改位於自己所在組或者下屬組之外的任何線程”(注釋①)。然而,我們很難判斷“修改”在這兒的具體含義是什麼。下面這個例子展示了位於一個“葉子組”內的線程能修改它所在線程組樹的所有線程的優先級,同時還能為這個“樹”內的所有線程都調用一個方法。
①:《The Java Programming Language》第179頁。該書由Arnold和Jams Gosling編著,Addison-Wesley於1996年出版
//: TestAccess.java // How threads can access other threads // in a parent thread group public class TestAccess { public static void main(String[] args) { ThreadGroup x = new ThreadGroup("x"), y = new ThreadGroup(x, "y"), z = new ThreadGroup(y, "z"); Thread one = new TestThread1(x, "one"), two = new TestThread2(z, "two"); } } class TestThread1 extends Thread { private int i; TestThread1(ThreadGroup g, String name) { super(g, name); } void f() { i++; // modify this thread System.out.println(getName() + " f()"); } } class TestThread2 extends TestThread1 { TestThread2(ThreadGroup g, String name) { super(g, name); start(); } public void run() { ThreadGroup g = getThreadGroup().getParent().getParent(); g.list(); Thread[] gAll = new Thread[g.activeCount()]; g.enumerate(gAll); for(int i = 0; i < gAll.length; i++) { gAll[i].setPriority(Thread.MIN_PRIORITY); ((TestThread1)gAll[i]).f(); } g.list(); } } ///:~
在main()中,我們創建了幾個ThreadGroup(線程組),每個都位於不同的“葉”上:x沒有參數,只有它的名字(一個String),所以會自動進入“system”(系統)線程組;y位於x下方,而z位於y下方。注意初始化是按照文字順序進行的,所以代碼合法。
有兩個線程創建之後進入了不同的線程組。其中,TestThread1沒有一個run()方法,但有一個f(),用於通知線程以及打印出一些東西,以便我們知道它已被調用。而TestThread2屬於TestThread1的一個子類,它的run()非常詳盡,要做許多事情。首先,它獲得當前線程所在的線程組,然後利用getParent()在繼承樹中向上移動兩級(這樣做是有道理的,因為我想把TestThread2在分級結構中向下移動兩級)。隨後,我們調用方法activeCount(),查詢這個線程組以及所有子線程組內有多少個線程,從而創建由指向Thread的句柄構成的一個數組。enumerate()方法將指向所有這些線程的句柄置入數組gAll裡。然後在整個數組裡遍歷,為每個線程都調用f()方法,同時修改優先級。這樣一來,位於一個“葉子”線程組裡的線程就修改了位於父線程組的線程。
調試方法list()打印出與一個線程組有關的所有信息,把它們作為標准輸出。在我們對線程組的行為進行調查的時候,這樣做是相當有好處的。下面是程序的輸出:
java.lang.ThreadGroup[name=x,maxpri=10] Thread[one,5,x] java.lang.ThreadGroup[name=y,maxpri=10] java.lang.ThreadGroup[name=z,maxpri=10] Thread[two,5,z] one f() two f() java.lang.ThreadGroup[name=x,maxpri=10] Thread[one,1,x] java.lang.ThreadGroup[name=y,maxpri=10] java.lang.ThreadGroup[name=z,maxpri=10] Thread[two,1,z]
list()不僅打印出ThreadGroup或者Thread的類名,也打印出了線程組的名字以及它的最高優先級。對於線程,則打印出它們的名字,並接上線程優先級以及所屬的線程組。注意list()會對線程和線程組進行縮排處理,指出它們是未縮排的線程組的“子”。
大家可看到f()是由TestThread2的run()方法調用的,所以很明顯,組內的所有線程都是相當脆弱的。然而,我們只能訪問那些從自己的system線程組樹分支出來的線程,而且或許這就是所謂“安全”的意思。我們不能訪問其他任何人的系統線程樹。
1. 線程組的控制
拋開安全問題不談,線程組最有用的一個地方就是控制:只需用單個命令即可完成對整個線程組的操作。下面這個例子演示了這一點,並對線程組內優先級的限制進行了說明。括號內的注釋數字便於大家比較輸出結果:
//: ThreadGroup1.java // How thread groups control priorities // of the threads inside them. public class ThreadGroup1 { public static void main(String[] args) { // Get the system thread & print its Info: ThreadGroup sys = Thread.currentThread().getThreadGroup(); sys.list(); // (1) // Reduce the system thread group priority: sys.setMaxPriority(Thread.MAX_PRIORITY - 1); // Increase the main thread priority: Thread curr = Thread.currentThread(); curr.setPriority(curr.getPriority() + 1); sys.list(); // (2) // Attempt to set a new group to the max: ThreadGroup g1 = new ThreadGroup("g1"); g1.setMaxPriority(Thread.MAX_PRIORITY); // Attempt to set a new thread to the max: Thread t = new Thread(g1, "A"); t.setPriority(Thread.MAX_PRIORITY); g1.list(); // (3) // Reduce g1's max priority, then attempt // to increase it: g1.setMaxPriority(Thread.MAX_PRIORITY - 2); g1.setMaxPriority(Thread.MAX_PRIORITY); g1.list(); // (4) // Attempt to set a new thread to the max: t = new Thread(g1, "B"); t.setPriority(Thread.MAX_PRIORITY); g1.list(); // (5) // Lower the max priority below the default // thread priority: g1.setMaxPriority(Thread.MIN_PRIORITY + 2); // Look at a new thread's priority before // and after changing it: t = new Thread(g1, "C"); g1.list(); // (6) t.setPriority(t.getPriority() -1); g1.list(); // (7) // Make g2 a child Threadgroup of g1 and // try to increase its priority: ThreadGroup g2 = new ThreadGroup(g1, "g2"); g2.list(); // (8) g2.setMaxPriority(Thread.MAX_PRIORITY); g2.list(); // (9) // Add a bunch of new threads to g2: for (int i = 0; i < 5; i++) new Thread(g2, Integer.toString(i)); // Show information about all threadgroups // and threads: sys.list(); // (10) System.out.println("Starting all threads:"); Thread[] all = new Thread[sys.activeCount()]; sys.enumerate(all); for(int i = 0; i < all.length; i++) if(!all[i].isAlive()) all[i].start(); // Suspends & Stops all threads in // this group and its subgroups: System.out.println("All threads started"); sys.suspend(); // Deprecated in Java 1.2 // Never gets here... System.out.println("All threads suspended"); sys.stop(); // Deprecated in Java 1.2 System.out.println("All threads stopped"); } } ///:~
下面的輸出結果已進行了適當的編輯,以便用一頁能夠裝下(java.lang.已被刪去),而且添加了適當的數字,與前面程序列表中括號裡的數字對應:
(1) ThreadGroup[name=system,maxpri=10] Thread[main,5,system] (2) ThreadGroup[name=system,maxpri=9] Thread[main,6,system] (3) ThreadGroup[name=g1,maxpri=9] Thread[A,9,g1] (4) ThreadGroup[name=g1,maxpri=8] Thread[A,9,g1] (5) ThreadGroup[name=g1,maxpri=8] Thread[A,9,g1] Thread[B,8,g1] (6) ThreadGroup[name=g1,maxpri=3] Thread[A,9,g1] Thread[B,8,g1] Thread[C,6,g1] (7) ThreadGroup[name=g1,maxpri=3] Thread[A,9,g1] Thread[B,8,g1] Thread[C,3,g1] (8) ThreadGroup[name=g2,maxpri=3] (9) ThreadGroup[name=g2,maxpri=3] (10)ThreadGroup[name=system,maxpri=9] Thread[main,6,system] ThreadGroup[name=g1,maxpri=3] Thread[A,9,g1] Thread[B,8,g1] Thread[C,3,g1] ThreadGroup[name=g2,maxpri=3] Thread[0,6,g2] Thread[1,6,g2] Thread[2,6,g2] Thread[3,6,g2] Thread[4,6,g2] Starting all threads: All threads started
所有程序都至少有一個線程在運行,而且main()采取的第一項行動便是調用Thread的一個static(靜態)方法,名為currentThread()。從這個線程開始,線程組將被創建,而且會為結果調用list()。輸出如下:
(1) ThreadGroup[name=system,maxpri=10] Thread[main,5,system]
我們可以看到,主線程組的名字是system,而主線程的名字是main,而且它從屬於system線程組。
第二個練習顯示出system組的最高優先級可以減少,而且main線程可以增大自己的優先級:
(2) ThreadGroup[name=system,maxpri=9] Thread[main,6,system]
第三個練習創建一個新的線程組,名為g1;它自動從屬於system線程組,因為並沒有明確指定它的歸屬關系。我們在g1內部放置了一個新線程,名為A。隨後,我們試著將這個組的最大優先級設到最高的級別,並將A的優先級也設到最高一級。結果如下:
(3) ThreadGroup[name=g1,maxpri=9] Thread[A,9,g1]
可以看出,不可能將線程組的最大優先級設為高於它的父線程組。
第四個練習將g1的最大優先級降低兩級,然後試著把它升至Thread.MAX_PRIORITY。結果如下:
(4) ThreadGroup[name=g1,maxpri=8] Thread[A,9,g1]
同樣可以看出,提高最大優先級的企圖是失敗的。我們只能降低一個線程組的最大優先級,而不能提高它。此外,注意線程A的優先級並未改變,而且它現在高於線程組的最大優先級。也就是說,線程組最大優先級的變化並不能對現有線程造成影響。
第五個練習試著將一個新線程設為最大優先級。如下所示:
(5) ThreadGroup[name=g1,maxpri=8] Thread[A,9,g1] Thread[B,8,g1]
因此,新線程不能變到比最大線程組優先級還要高的一級。
這個程序的默認線程優先級是6;若新建一個線程,那就是它的默認優先級,而且不會發生變化,除非對優先級進行了特別的處理。練習六將把線程組的最大優先級降至默認線程優先級以下,看看在這種情況下新建一個線程會發生什麼事情:
(6) ThreadGroup[name=g1,maxpri=3] Thread[A,9,g1] Thread[B,8,g1] Thread[C,6,g1]
盡管線程組現在的最大優先級是3,但仍然用默認優先級6來創建新線程。所以,線程組的最大優先級不會影響默認優先級(事實上,似乎沒有辦法可以設置新線程的默認優先級)。
改變了優先級後,接下來試試將其降低一級,結果如下:
(7) ThreadGroup[name=g1,maxpri=3] Thread[A,9,g1] Thread[B,8,g1] Thread[C,3,g1]
因此,只有在試圖改變優先級的時候,才會強迫遵守線程組最大優先級的限制。
我們在(8)和(9)中進行了類似的試驗。在這裡,我們創建了一個新的線程組,名為g2,將其作為g1的一個子組,並改變了它的最大優先級。大家可以看到,g2的優先級無論如何都不可能高於g1:
(8) ThreadGroup[name=g2,maxpri=3] (9) ThreadGroup[name=g2,maxpri=3]
也要注意在g2創建的時候,它會被自動設為g1的線程組最大優先級。
經過所有這些實驗以後,整個線程組和線程系統都會被打印出來,如下所示:
(10)ThreadGroup[name=system,maxpri=9] Thread[main,6,system] ThreadGroup[name=g1,maxpri=3] Thread[A,9,g1] Thread[B,8,g1] Thread[C,3,g1] ThreadGroup[name=g2,maxpri=3] Thread[0,6,g2] Thread[1,6,g2] Thread[2,6,g2] Thread[3,6,g2] Thread[4,6,g2]
所以由線程組的規則所限,一個子組的最大優先級在任何時候都只能低於或等於它的父組的最大優先級。
本程序的最後一個部分演示了用於整組線程的方法。程序首先遍歷整個線程樹,並啟動每一個尚未啟動的線程。例如,system組隨後會被掛起(暫停),最後被中止(盡管用suspend()和stop()對整個線程組進行操作看起來似乎很有趣,但應注意這些方法在Java 1.2裡都是被“反對”的)。但在掛起system組的同時,也掛起了main線程,而且整個程序都會關閉。所以永遠不會達到讓線程中止的那一步。實際上,假如真的中止了main線程,它會“擲”出一個ThreadDeath違例,所以我們通常不這樣做。由於ThreadGroup是從Object繼承的,其中包含了wait()方法,所以也能調用wait(秒數×1000),令程序暫停運行任意秒數的時間。當然,事前必須在一個同步塊裡取得對象鎖。
ThreadGroup類也提供了suspend()和resume()方法,所以能中止和啟動整個線程組和它的所有線程,也能中止和啟動它的子組,所有這些只需一個命令即可(再次提醒,suspend()和resume()都是Java 1.2所“反對”的)。
從表面看,線程組似乎有些讓人摸不著頭腦,但請注意我們很少需要直接使用它們。