上一部分向您介绍Thread的activeCount()和enumerate(Thread[] thdarray)方法。而enumerate(Thread [] thdarray)只是以下四种枚举方法之一:
1. int enumerate(Thread [] thdarray)复制到thdarray当前线程组和所有子组中每个活动线程的引用。
2. int enumerate(Thread [] thdarray, boolean recurse)thdarray仅当recurse为false时,才复制到当前线程组中每个活动线程的引用。否则,此方法包括来自子组的活动线程。
3. int enumerate(ThreadGroup [] tgarray)复制到tgarray当前线程组中每个活动子组的引用。
4. int enumerate(ThreadGroup [] tgarray, boolean recurse)tgarray仅当recurse为false时,才复制到当前线程组中每个活动子组的引用。否则,此方法包括活动子组的所有活动子组,活动子组的活动子组的活动子组,等等。
您可以使用ThreadGroup's' activeCount和enumerate(Thread [] thdarray)方法枚举所有程序线程。首先,找到system线程组。然后调用ThreadGroup的activeCount()方法来检索活动线程计数阵列上浆的目的。接下来,您调用ThreadGroup的enumerate(Thread [] thdarray)方法来填充与数组Thread引用,如实例4所示:
运行时,EnumThreads产生以下输出:
除了一个main线程,所有其他线程都属于system线程组。
波动性
波动性,即可变性,描述了一个线程更改共享字段变量的值而另一个线程看到该更改的情况。您希望其他线程始终看到共享字段变量的值,但情况不一定如此。出于性能原因,Java不需要JVM实现从主内存或对象堆内存中读取值或将值写入共享字段变量。相反,JVM可能会从处理器寄存器或缓存中读取共享字段变量的值,统称为工作内存。同样,JVM可能会将共享字段变量的值写入处理器寄存器或缓存。该功能会影响线程共享字段变量的方式。
假设程序创建一个共享整数字段变量,x其主存储器中的初始值为10.该程序启动两个线程; 一个线程写入x,另一个读取x的值。最后,该程序在JVM实现上运行,该实现为每个线程分配自己的私有工作内存,这意味着每个线程都有自己的私有副本x。当写入线程写入x为6时,写入线程仅更新其私有工作内存副本x; 线程不更新主内存副本。此外,当读取线程读取时x,返回的值来自阅读线程的私有副本。因此,读取线程返回10(因为共享字段变量的私有工作内存副本初始化为取自主内存对应的值),而不是6.因此,一个线程不知道另一个线程对共享字段变量的更改。
线程无法观察另一个线程对共享字段变量的修改可能会导致严重问题。例如,当你跑步时YieldDemo,你可能发现程序最终终止了。这意味着您的JVM实现读/写主内存而不是工作内存。但是如果你发现程序没有终止,你可能会遇到这样一种情况:主线程将其工作内存副本设置finished为true,而不是等效的主内存副本。此外,YieldDemo线程读取自己的工作内存副本finished,并不知道真实情况。
要修复YieldDemo可见性问题,请volatile在finished和sum声明中包含Java的关键字:static volatile boolean finished = false;和static volatile int sum = 0;。的volatile关键字确保当一个线程写入到易失性共享场变量,该JVM修改主存储器副本,而不是线程的工作记忆拷贝。同样,JVM确保线程始终从主内存副本中读取。
注意:在volatile和final在共享字段变量声明关键字不能一起出现。任何包含两个关键字的尝试都会强制编译器报告错误。
当线程使用同步访问共享字段变量时,不会发生可见性问题。新开发人员有时会认为波动性取代了同步。尽管volatile(通过关键字volatile)允许您在同步上下文之外为长整数或双精度浮点共享字段变量赋值,但波动率不能取代同步。通过同步,您可以将多个操作分组到一个不可分割的单元中,而这与波动性无关。但是,由于波动性比同步更快,因此在多个线程必须通过单个共享字段变量进行通信的情况下使用波动率。
线程局部变量
Sun的Java 2平台标准版(J2SE)SDK 1.2引入了java.lang.ThreadLocal该类,开发人员使用该类创建线程局部变量- ThreadLocal基于每个线程存储值的对象。每个ThreadLocal对象为访问该对象的每个线程维护一个单独的值(例如用户ID)。此外,线程操纵自己的值,不能访问同一线程局部变量中的其他值。
ThreadLocal 有三种方法:
1. Object get ():从线程局部变量返回调用线程的值。因为此方法是线程安全的,所以您可以get()从外部调用同步上下文。
2. Object initialValue ():从线程局部变量返回调用线程的初始值。每个线程首次调用get()或者set(Object value)导致间接调用initialValue()以初始化线程局部变量中该线程的值。因为返回null ThreadLocal的默认实现initialValue(),所以必须子类化ThreadLocal并重写此方法以返回非空的初始值。
3. void set (Object value):将线程局部变量中的当前线程值设置为value。使用此方法替换initialValue()返回的值。
实例5演示了如何使用ThreadLocal:
ThreadLocalDemo1创建一个线程局部变量。该变量将唯一的序列号与通过调用访问线程局部变量的每个线程相关联tl.get ()。当一个线程第一次调用时tl.get (),ThreadLocal's get()方法调用initialValue()匿名ThreadLocal子类中的重写方法。以下输出来自一个程序调用:
输入的每个线程名具有唯一的序列号。
如果再次运行此程序,您可能会看到与线程名称关联的其他序列号。虽然数字不同,但它始终与单个线程名称相关联。
计时器
程序偶尔需要一个定时器机制来执行一次或定期执行代码,并在某个指定时间或在一段时间间隔之后执行。在Sun发布J2SE 1.3之前,开发人员要么创建了自定义计时器机制,要么依赖于另一个机制。不兼容的计时器机制导致难以维护的源代码。认识到需要标准化计时器机制,Sun在SDK 1.3中引入了两个计时器类:java.util.Timer和java.util.TimerTask。
根据SDK,使用Timer对象来安排任务- TimerTask以及子类对象 - 以便执行。该执行依赖于与该Timer对象关联的线程。要创建Timer对象,请调用Timer()或Timer(boolean isDaemon)构造函数。构造函数在创建执行任务时Timer()创建的线程不同:创建非Timer(boolean isDaemon)守护程序线程,而在isDaemon包含true 时创建守护程序线程。以下代码演示了两个构造函数创建Timer对象:
创建Timer对象后,需要TimerTask执行。
既然你有一个Timer对象和一个TimerTask子类,为了TimerTask一次性或重复执行一个对象,请调用以下Timer四种schedule()方法之一:
1. void schedule(TimerTask task, Date time):task在指定的时间执行一次性计划time。
2. void schedule(TimerTask task, Date firstTime, long interval):以下task指定的间隔firstTime和interval毫秒间隔重复执行的计划firstTime。此执行称为固定延迟执行,因为每次后续task执行都相对于先前task执行的实际执行时间发生。此外,如果由于垃圾收集或某些其他后台活动导致执行延迟,则所有后续执行也会延迟。
3. void schedule(TimerTask task, long delay):task在delay毫秒通过后进行一次性执行的计划。
4. void schedule(TimerTask task, long delay, long interval):task在delay毫秒通过后以及以interval毫秒为间隔重复执行的计划firstTime。该方法采用固定延迟执行。
假定一个t1引用Timer对象的下面的代码片段创建一个MyTask对象,并使用上面列表中的第四个方法调度该对象,以便在零毫秒的初始延迟之后重复执行
每秒(即1,000毫秒),该Timer线程执行MyTask的run()方法。有关任务执行的更有用示例,请查看实例8:
Clock1创建一个Timer对象并调用schedule(TimerTask task, long delay, long interval)来使TimerTask子类对象run()方法的固定延迟执行。该方法通过调用获取当前日期java.util.Date的Date()构造函数和转换Date对象的内容。以下部分输出显示运行此程序的结果:
除了这四种schedule()方法外,Timer还包括两种scheduleAtFixedRate()方法:
1. void scheduleAtFixedRate(TimerTask task, Date firstTime, long interval):以下task指定的间隔firstTime和interval毫秒间隔重复执行的计划firstTime。此执行称为固定速率执行,因为每次后续task执行都是相对于初始task执行发生的。此外,如果由于垃圾收集或一些其他后台活动而导致执行延迟,则快速连续发生两次或更多次执行以维持执行频率。
2. void scheduleAtFixedRate(TimerTask task, long delay, long interval):task在delay毫秒通过后以及以interval毫秒为间隔重复执行的计划firstTime。该方法采用固定速率执行。
总结
通过探索线程组,波动率,线程局部变量,定时器和ThreadDeath,掌握使用线程组对相关线程进行分组,使用使用volatile来允许线程访问共享字段变量的主内存副本,使用线程局部变量为线程提供自己独立初始化的值,使用定时器来安排执行任务周期性地或一次性执行,并ThreadDeath用于让线程过早地退出其run()方法。