分享
 
 
 

介绍JAVA的线程、线程类及Runnable (3)

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

用Java线程获取优异性能(I)——介绍线程、线程类及Runnable

Jeff Friesen 著 刘建华 编译

查询活跃线程

在有些情形下,你可能想了解在你的程序中哪些线程是激活的。Thread支持一对方法帮助你完成这个任务: activeCount()和 enumerate(Thread [] thdarray)。但那些方法只工作在当前线程的线程组中。换句话说,那些方法只识别属于当前线程的同一线程组的活跃线程。 (我将在以后的系列文章中讨论线程组——一种组织机制。)

静态activeCount()方法返回在当前线程的线程组中正在活跃运行的线程数量。一个程序利用这个方法的整数返回值设定一个Thread引用数组的大小。检索那些引用,程序必须调用静态enumerate(Thread [] thdarray)方法。这个方法的整数返回值确定Thread引用存贮在数组中的enumerate(Thread []thdarray)的总数。要看这些方法如何一起工作,请查看表6:

表6. Census.java

// Census.java

class Census

{

public static void main (String [] args)

{

Thread [] threads = new Thread [Thread.activeCount ()];

int n = Thread.enumerate (threads);

for (int i = 0; i < n; i++)

System.out.println (threads [i].toString ());

}

}

在运行时,这个程序会产生如下的输出:

Thread[main,5,main]

输出显示一个线程,开始线程正在运行。左边的main表示线程的名称。5显示线程的优先权,右边的main表示线程的线程组。你也许很失望不能在输出中看到任何系统线程,比如垃圾收集器线程。那种限制由Thread的enumerate(Thread [] thdarray) 方法产生,它仅询问当前线程线程组的活跃线程。然而, ThreadGroup类包含多种enumerate()方法允许你捕获对所有活跃线程的引用而不管线程组。在稍后的系列中,探讨ThreadGroup时我将向你显示如何列举所有的引用。

警告

当重申一个数组时不要依靠activeCount()的返回值。如果你这样做了,你的程序将冒掷出一个NullPointerException对象的风险。为什么呢?在调用activeCount()和enumerate(Thread [] thdarray)之间,一个或更多线程可能结束。结果, enumerate(Thread [] thdarray)能够复制少数线程引用进它的数组。因此,仅考虑将activeCount()的返回值作为数组可能大小的最大值。同样,考虑将enumerate(Thread [] thdarray)的返回值作为在一个程序对那种方法调用时活跃线程的数目。

反臭虫

如果你的程序出现故障并且你怀疑问题出在线程,通过调用Thread的dumpStack()和toString()方法你能够了解到线程的更多细节。静态dumpStack()方法提供一个new Exception ("Stack trace").printStackTrace ()的封装,打印一个追踪当前线程的堆栈。toString()依据下面格式返回一个描述线程的名称、优先权和线程组的字符串: Thread[thread-name,priority,thread-group]. (在稍后的系列中你将学到更多关于优先权的知识。)

技巧

在一些地方,这篇文章提到了当前线程的概念。如果你需要访问描述当前线程的Thread对象,则调用Thread的静态currentThread()方法。例:Thread current = Thread.currentThread ()。

等级系统

不是所有线程都被平等创建。它们被分成两类:用户和监督。一个用户线程执行着对于程序用户十分重要的工作,工作必须在程序结束前完成。相反,一个监督线程执行着后勤事务(比如垃圾收集)和其它可能不会对应用程序的主要工作作出贡献但对于应用程序继续它的主要工作却非常必要的后台任务。和用户线程不一样,监督线程不需要在应用程序结束前完成。当一个应用程序的开始线程(它是一个用户线程)结束时,JVM检查是否还有其它用户线程正在运行。如果有,JVM就会阻止应用程序结束。否则,JVM就会结束应用程序而不管监督线程是否正在运行。

当一个线程调用一个线程对象的start()方法时,新的已经开始的线程就是一个用户线程。那是缺省的。要建立一个线程作为监督线程,程序必须在调用start()前调用Thread的一个带布尔真值参数的setDaemon(boolean isDaemon)方法。稍后,你可以通过调用Thread的isDaemon()方法检查一个线程是否是监督线程。如果是监督线程那个方法返回一个布尔真值。

为了让你试试用户和监督线程,我写了一个UserDaemonThreadDemo:

表7. UserDaemonThreadDemo.java

// UserDaemonThreadDemo.java

class UserDaemonThreadDemo

{

public static void main (String [] args)

{

if (args.length == 0)

new MyThread ().start ();

else

{

MyThread mt = new MyThread ();

mt.setDaemon (true);

mt.start ();

}

try

{

Thread.sleep (100);

}

catch (InterruptedException e)

{

}

}

}

class MyThread extends Thread

{

public void run ()

{

System.out.println ("Daemon is " + isDaemon ());

while (true);

}

}

编译了代码后,通过Java2 SDK的java命令运行UserDaemonThreadDemo。如果你没有使用命令行参数运行程序,例如java UserDaemonThreadDemo, new MyThread ().start ()执行。这段代码片断开始一个在进入一个无限循环前打印Daemon is false的用户线程。(你必须按Ctrl-C或一个等价于结束一个无限循环的组合按键。)因为新线程是一个用户线程,应用程序在开始线程结束后仍保持运行。然而,如果你指定了至少一个命令行参数,例如java UserDaemonThreadDemo x,mt.setDaemon (true)执行并且新线程将是一个监督线程。结果,一旦开始线程从100毫秒休眠中醒来并结束,新的监督线程也将结束。

警告

如果线程开始执行后调用setDaemon(boolean isDaemon)方法,setDaemon(boolean isDaemon)方法将掷出一个IllegalThreadStateException对象。

Runnable

学习前面部份的例子后,你可能认为引入多线程进入一个类总是要求你去扩展Thread并将你的子类重载Thread's run()方法。然而那并不总是一种选择。Java对继承的强制执行禁止一个类扩展两个或更多个超类。结果,如果一个类扩展了一个无线程类,那个类就不能扩展Thread. 假使限制,怎样才可能将多线程引入一个已经扩展了其它类的类?幸运的是, Java的设计者已经意识到不可能创建Thread子类的情形总会发生的。这导致产生java.lang.Runnable接口和带Runnable参数的Thread构造器,如Thread(Runnable target)。

Runnable接口声明了一个单独方法署名:void run()。这个署名和Thread的run()方法署名一样并作为线程的执行入口服务。因为Runnable是一个接口,任何类都能通过将一个implements子句包含进类头和提供一个适当的run()方法实现接口。在执行时间,程序代码能从那个类创建一个对象或runnable并将runnable的引用传递给一个适当的Thread构造器。构造器和Thread对象一起存贮这个引用并确保一个新线程在调用Thread对象的start()方法后调用runnable的run()方法。示范如表8:

表8.RunnableDemo.java

// RunnableDemo.java

class RunnableDemo

{

public static void main (String [] args)

{

Rectangle r = new Rectangle (5, 6);

r.draw ();

//用随机选择的宽度和高度画不同的长方形

new Rectangle ();

}

}

abstract class Shape

{

abstract void draw ();

}

class Rectangle extends Shape implements Runnable

{

private int w, h;

Rectangle ()

{

//创建一个绑定这个runnable的新Thread对象并开始一个将调用这个runnable的

//run()方法的线程

new Thread (this).start ();

}

Rectangle (int w, int h)

{

if (w < 2)

throw new IllegalArgumentException ("w value " + w + " < 2");

if (h < 2)

throw new IllegalArgumentException ("h value " + h + " < 2");

this.w = w;

this.h = h;

}

void draw ()

{

for (int c = 0; c < w; c++)

System.out.print ('*');

System.out.print ('\n');

for (int r = 0; r < h - 2; r++)

{

System.out.print ('*');

for (int c = 0; c < w - 2; c++)

System.out.print (' ');

System.out.print ('*');

System.out.print ('\n');

}

for (int c = 0; c < w; c++)

System.out.print ('*');

System.out.print ('\n');

}

public void run ()

{

for (int i = 0; i < 20; i++)

{

w = rnd (30);

if (w < 2)

w += 2;

h = rnd (10);

if (h < 2)

h += 2;

draw ();

}

}

int rnd (int limit)

{

//在0<=x<界限范围内返回一个随机数字x

return (int) (Math.random () * limit);

}

}

RunnableDemo由类RunnableDemo,Shape和Rectangle组成。类RunnableDemo通过创建一个Rectangle对象驱动应用程序—通过调用对象的draw()方法—和通过创建第二个什么都不做的Rectangle类。相反,Shape和Rectangle组成了一个基于shape层次的类。Shape是抽象的因为它提供一个抽象的draw()方法。各种shape类,比如Rectangle,扩展Shape和描述它们如何画它们自己的重载draw()。以后,我可能决定引入一些另外的shape类,创建一个Shape数组,通过调用Shape的draw()方法要求每一个Shape元素画它自己。

RunnableDemo 作为一个不带多线程的简单程序产生。后面我决定引入多线程到Rectangle,这样我能够用各种宽度和高度画种种矩形。因为Rectangle扩展Shape (为了以后的多态性原因),我没有其它选择只有让Rectangle实现Runnable。同样,在Rectangle()构造器内,我不得不将一个Rectangle runnable绑定到一个新的Thread对象并调用Thread的start()方法开始一个新的线程调用Rectangle的run()方法画矩形。

因为包括在这篇文章中的RunnableDemo的新输出太长了,我建议你自己编译并运行程序。

技巧

当你面对一个类不是能扩展Thread就是能实现Runnable的情形时,你将选择哪种方法?如果这个类已经扩展了其它类,你必须实现Runnable。然而,如果这个类没有扩展其它类,考虑一下类的名称。名称将暗示这个类的对象不是积极的就是消极的。例如,名称Ticker暗示它的对象是积极的。因此,Ticker类将扩展Thread,并且Ticker对象将被作为专门的Thread对象。相反,Rectangle暗示消极对象—Rectangle对象对于它们自己什么也不做。因此,Rectangle类将实现Runnable,并且Rectangle 对象将使用Thread对象(为了测试或其它意图)代替成为专门的Thread对象。

回顾

用户期望程序达到优异的性能。一种办法是用线程完成那些任务。一个线程是一条程序代码的独立执行通道。线程有益于基于GUI的程序,因为它们允许那些程序当执行其它任务时仍对用户保持响应。另外,带线程的程序比它们没带线程的副本程序完成的快。这对于运行在多处理器机器上的情形尤其明显,在这里每一个线程有它自己的处理器。Thread和Thread子类对象描述了线程并与那些实体相关。对于那些不能扩展Thread的类,你必须创建一个runnable以利用多线程的优势。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有