- 写在前面的话
- 引言
- 第 1 章 对象入门
- 第 2 章 一切都是对象
- 第 3 章 控制程序流程
- 第 4 章 初始化和清除
- 第 5 章 隐藏实施过程
- 第 6 章 类再生
- 第 7 章 多形性
- 第 8 章 对象的容纳
- 第 9 章 违例差错控制
- 第 10 章 Java IO 系统
- 第 11 章 运行期类型鉴定
- 第 12 章 传递和返回对象
- 第 十三 章 创建窗口和程序片
- 第 14 章 多线程
- 第 15 章 网络编程
- 第 16 章 设计范式
- 第 17 章 项目
- 附录 A 使用非 JAVA 代码
- 附录 B 对比 C++和 Java
- 附录 C Java 编程规则
- 附录 D 性能
- 附录 E 关于垃圾收集的一些话
- 附录 F 推荐读物
14.4.1 线程组
所有线程都隶属于一个线程组。那可以是一个默认线程组,亦可是一个创建线程时明确指定的组。在创建之初,线程被限制到一个组里,而且不能改变到一个不同的组。每个应用都至少有一个线程从属于系统线程组。若创建多个线程而不指定一个组,它们就会自动归属于系统线程组。
线程组也必须从属于其他线程组。必须在构建器里指定新线程组从属于哪个线程组。若在创建一个线程组的时候没有指定它的归属,则同样会自动成为系统线程组的一名属下。因此,一个应用程序中的所有线程组最终都会将系统线程组作为自己的“父”。
之所以要提出“线程组”的概念,很难从字面上找到原因。这多少为我们讨论的主题带来了一些混乱。一般地说,我们认为是由于“安全”或者“保密”方面的理由才使用线程组的。根据 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 所“反对”的)。
从表面看,线程组似乎有些让人摸不着头脑,但请注意我们很少需要直接使用它们。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论