线程的管理 
线程启动之后,通常需要对线程的运行进行管理,本章主要介绍线程管理的方法。
3.1 线程数目的确定
在并行编程过程中,很多情况下会有这样的问题:在程序中创建多少个线程最合适? 创建多少个
线程才能使程序性能最佳? 
线程数目的多少对多核处理器性能的发挥有一定的影响。如果创建线程的数目较少,则会导致多
核处理器的多个处理核处于闲置状态,性能得不到完全发挥,浪费了系统的资源。如果创建线程的数
目过多,则由于多核处理器的处理能力是一定的,多余的线程不能马上得到执行,浪费了软件资源,这
也会在一定程度上影响程序的性能。可见,在并行程序中创建线程的数目过多或过少都不好。
在解决一个大的问题时,通常希望创建足够多的线程数,以满足计算的需求,然而在很多情况下, 
在程序中设定线程数为多核处理器可以同时处理的最大线程数,Java中提供了相关的方法,可用于获
取处理器可以同时处理的最大线程数,该方法如下: 
int nthreads = Runtime.getRuntime().availableProcessors(); 
其中,availableProcessors()方法得到的是Java虚拟机可以使用的逻辑意义上的处理核数N,显然
N是一个整型值。例如,如果一个多核处理器有4个处理核,并且该处理器不支持超线程,则通过调用
方法availableProcessors()得到的值为4;如果该多核处理器支持超线程,则通过调用方法
availableProcessors()得到的值为8。
通过查看Windows操作系统的任务管理器可以查看处理器的逻辑核数。图3-1展示了两颗Intel 
XeonCPUE5-2650多核处理器的CPU 处理核的使用情况,每颗CPU 有8个处理核,每个核均支持超
线程,从图3-1可以看出,该处理器可以支持32个线程同时运行。
在选择线程数时,可以根据具体情况决定。如果程序中所有任务都是计算密集型任务,则设定线
程数为多核处理器可以同时处理的最大线程数。计算密集型指程序的大部分时间用于计算,而非等待
或阻塞。如果阻塞的时间占很大的比例,则不属于计算密集型。
如果程序中的大部分任务是I/O 密集型任务,则应创建尽可能多的线程,这是因为当一个线程执
行的任务遇到I/O 操作时,该线程将阻塞,这时处理器将进行环境切换,继而转为其他线程执行,这样
可以让处理核一直处于忙碌状态,以提高处理器的利用率。
线程数的选取和程序阻塞时间也有一定的关系,设定任务的阻塞时间占任务完成总时间的比例为
f,显然,0≤f<1。计算线程总数t 的公式可以表示如下:

第3章 线程的管理 43 
图3-1 16核CPU 支持32线程性能情况
t= 处理器的逻辑处理核数N 
1-f 
如果一个程序中的所有任务都会有f=50%的阻塞时间,则此时设定的线程数应为多核处理器逻
辑核数的两倍。当f=0时,表示每个任务都处于一直计算的忙碌状态,没有任何等待,则可以设定线
程数t 为多核处理器的逻辑处理核数;当f 接近1时,表示很多任务都有相当多的阻塞时间,应尽可能
多地设定线程数。
3.2 线程运行的控制
线程启动以后可以管理线程,使线程休眠、等待或中断执行等。
3.2.1 等待线程执行完毕
在某些情况下,需要让某一个线程等待另一个线程执行结束后再开始执行该线程。例如,线程1 
和线程2共同完成一个计算并输出结果的任务,线程1完成相关的计算,线程2用于输出结果,显然, 
线程2需要等待线程1计算出结果后再输出。
可以使用线程类的方法join()实现上述功能,当调用某个线程类对象实例的join()方法后,将会等
待该线程类对象执行结束。
方法join()的定义形式如下: 
//没有参数的方法
. public final void join() throws InterruptedException 
//参数millis 为等待的毫秒数
. public final synchronized void join(long millis) throws InterruptedException 
//参数millis 为等待的毫秒数,nanos 为等待的纳秒数
. public final synchronized void join(long millis, int nanos) throws InterruptedException

44 并行编程
从上面的方法定义可以看出,方法join()可能会抛出InterruptedException异常,所以使用join() 
方法的代码需要包含在try…catch…代码语句中,形式如下: 
Thread t = new Thread(); 
try{ 
t.join(); 
}catch(InterruptedException e){ 
//… 
}
下面通过一个例题演示方法join()的使用。
【例3-1】 定义3个线程,线程A 用于产生若干随机数,线程B用于计算这些数的和,线程C用于
输出结果,只有当线程A 完成后,线程B才能计算,计算完成后,线程C才能输出。
【解题分析】
题意明确规定了线程之间的先后关系,可以通过方法join()控制。需要注意的是,在线程B执行
前,线程A 需要执行完毕,所以在线程B的run()方法中加入线程A 的join()方法。
【程序代码】 
//Producer.java 
package book.ch3.join; 
//定义类Producer,实现了Runnable 接口,代表题目中的线程A 
public class Producer implements Runnable{ 
//定义数组arr 
int[] arr; 
//构造方法定义 
public Producer(int[] arr){ 
//对类属性赋值 
this.arr = arr; 
} 
//run()方法定义 
public void run(){ 
//对数组元素进行初始化赋值 
for(int i=0; i<arr.length; i++){ 
arr[i] = (int)(Math.random()*100); 
} 
} 
} //Worker.java 
package book.ch3.join; 
//定义类Worker,该类实现了Runnable 接口,代表题目中的线程B 
public class Worker implements Runnable { 
//定义数组arr 
int[] arr; 
//定义线程对象thread

第3章 线程的管理 45 
Thread thread; 
//构造方法定义 
public Worker(int[] arr, Thread thread){ 
this.arr = arr; 
this.thread = thread; 
} 
//run()方法定义 
public void run(){ 
//使用join()方法,等待thread 线程执行完毕 
try { 
thread.join(); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 
//数组中各元素求和 
int sum = 0; 
for(int i=0; i<arr.length; i++){ 
sum += arr[i]; 
} 
//将求和结果sum 赋给全局静态变量sum 
Index.sum = sum; 
} 
} //PrintTask.java 
package book.ch3.join; 
//定义类PrintTask,该类实现了Runnable 接口,代表题目中的线程B 
public class PrintTask implements Runnable{ 
//定义线程对象thread 
Thread thread; 
//构造方法定义,对类属性thread 赋值 
PrintTask(Thread thread){ 
this.thread = thread; 
} 
//run()方法定义 
public void run(){ 
//通过join()方法,让thread 线程等待 
try { 
thread.join(); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 
//输出结果 
System.out.println("sum="+Index.sum); 
} 
}

46 并行编程 
//Index.java 
package book.ch3.join; 
public class Index { 
//静态全局变量sum,初始值为0 
static int sum = 0; 
public static void main(String[] args) { 
//定义数组arr,大小为100000 
int[] arr = new int[100000]; 
//创建线程p 并启动 
Thread p = new Thread(new Producer(arr)); 
p.start(); 
//创建线程w 并启动 
Thread w = new Thread(new Worker(arr, p)); 
w.start(); 
//创建线程o 并启动 
Thread o = new Thread(new PrintTask(w)); 
o.start(); 
//等待线程结束 
try { 
o.join(); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 
//校验结果 
int t = 0; 
for (int i = 0; i < arr.length; i++) { 
t += arr[i]; 
} 
if (t == sum) { 
System.out.println("验证通过!"); 
} else { 
System.out.println("验证失败. t=" + t + ", sum=" + sum); 
} 
} 
}
【运行结果】
程序运行结果如图3-2所示。由于数据随机产生,因此sum 的计算结果每次都不同。
图3-2 运行结果

第3章 线程的管理 47 
【相关讨论】
该例让一个线程等待另一个线程执行完毕,也可以让一个线程等待若干线程执行完毕,读者可以
自行练习。
3.2.2 休眠
方法sleep()用于使一个线程暂停运行一段固定的时间,暂停时间的具体长短由sleep()方法的参
数给出。方法sleep()的定义形式如下: 
//参数millis 指明了休眠的毫秒数
. public static native void sleep(long millis) throws InterruptedException 
//参数millis 指明了休眠的毫秒数,nanos 指明了休眠的纳秒数
. public static void sleep(long millis, int nanos) throws InterruptedException 
方法sleep()可能会抛出InterruptedException异常,因此需要将该方法放入try…catch…语句中。
在线程暂停执行的这段时间中,CPU 的时间片会让给其他线程,从而使其他线程可以交由CPU 
执行。线
程的调度是按照线程的优先级顺序进行的。当高优先级的线程存在时,低优先级的线程获得
CPU 的机会很小。有时,高优先级的线程需要与低优先级的线程同步,此时高优先级的线程将会让出
CPU,使低优先级的线程有机会运行。高优先级的线程可以通过在它的run()方法中调用sleep()方法
使自己退出CPU 休眠一段时间,休眠结束后,如果条件具备,线程即可进入运行状态。
【例3-2】 输出0~10,每输出一个数字后线程休眠1s。
【解题分析】
在输出一个数字后,调用线程的方法sleep(),sleep的参数millis为毫秒数,故应使用1000ms。
【程序代码】 
//Worker.java 
package book.ch3.sleep; 
public class Worker extends Thread { 
public void run() { 
//循环11 次,每次间隔半秒输出i 的值 
for (int i = 0; i <= 10; i++) { 
System.out.print(" " + i); 
//线程休眠1000ms 
try{ 
sleep(1000); 
}catch(InterruptedException e){ } 
} 
} 
} //Index.java 
package book.ch3.sleep; 
public class Index {

48 并行编程 
public static void main(String[] args) { 
//创建两个线程对象worker1 和worker2 
Thread worker1 = new Worker(); 
Thread worker2 = new Worker(); 
worker1.start(); 
worker2.start(); 
} 
}
【运行结果】
程序运行结果如图3-3所示。
图3-3 运行结果
【相关讨论】
如果在同步语句中使用sleep()方法,则线程在睡眠期间不会丢掉对于任何监视器的持有权。
3.2.3 中断
一个线程除了正常执行结束外,也可以人为地中断线程的执行,线程的中断可以使用interrupt() 
方法。除
非是一个线程正在尝试中断它本身,否则中断的请求一般都会被接受。如果一个线程由于调用
wait()方法或join()方法正处于阻塞队列中,则中断请求不会被响应,并会抛出InterruptedException 
异常。在
早期的Thread类方法中,还可以使用stop()方法终止一个线程的执行,但stop()方法现在已不
推荐使用。
在程序中调用线程的方法interrupt()后,通常需要在线程的run()方法中使用类Thread的方法
isInterrupted()进行判断,并根据判断结果执行相应的操作。
需要注意的是,interrupt()和isInterrupted()是两个非常类似的方法。interrupt()方法是线程的
一个静态方法,调用该方法会清除线程的中断状态;isInterrupted()是一个实例方法,主要用于检查是
否被中断,调用该方法不会清除线程的中断状态。
【例3-3】 在线程中从2000年开始输出所有闰年,直到线程被中断。
【解题分析】
闰年的判断方法:如果某一年份能够被4整除,并且不能被100整除,则该年份是闰年;如果某一
年份能够被400整除,则该年份也是闰年。
线程中需要通过无限循环不断地输出闰年,在主线程中等待一段时间后,向线程发出中断请求,线
程中通过方法isInterrupted()判断是否处于中断状态。

第3章 线程的管理 49 
【程序代码】 
//LeapYearPrinter.java 
package book.ch3.interrupt; 
public class LeapYearPrinter extends Thread{ 
public void run(){ 
//从2000 年开始 
int year = 2000; 
System.out.println("闰年包括:"); 
while(true){ 
//闰年判断 
if(year%4==0&&year%100!=0 || year%400==0){ 
System.out.println("闰年:"+year); 
} 
//判断线程是否被中断,如果是,则输出信息并返回 
if(isInterrupted()){ 
System.out.println("线程类LeapYearPrinter 已经被中断."); 
return; 
} 
year++; 
} 
} 
} //Index.java 
package book.ch3.interrupt; 
public class Index { 
public static void main(String[] args) { 
//创建线程对象 
Thread newThread = new LeapYearPrinter(); 
//启动线程 
newThread.start(); 
try { 
Thread.sleep(1); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 
//通过interrupt 方法中断线程 
newThread.interrupt(); 
} 
}
【程序分析】
线程不断输出闰年,并让线程在休眠1ms后中断线程。这里选择休眠1ms是为了使输出在可控
的范围内,避免输出过多,读者可以自行调节。

50 并行编程 
【运行结果】
程序运行结果如图3-4所示。
图3-4 运行结果
JDK1.0提供了线程的方法stop()和suspend(),这两个方法通常用于控制线程的停止和挂起,其
中,方法stop()用来直接终止线程,方法suspend()会一直阻塞当前线程,直到调用该线程的resume() 
方法。由于缺乏安全性和容易导致死锁等原因,这两个方法从JDK2.0版本开始不再推荐使用。
3.2.4 让出CPU 的使用权
为了防止某个线程独占CPU 资源,可以让当前执行的线程让出CPU 的使用权,yield()方法可以
实现该功能。
方法yield()用于使当前线程让出CPU 的使用权,但是这并不能保证CPU 接下来调用的不是该
线程。该
方法常用于线程的调试和测试环境,用于发现由于竞争条件而引起的错误,也可以用于一些并
行的数据结构设计中。
【例3-4】 令线程从1开始输出到5,当输出到3时,令线程让出CPU 的使用权。
【解题分析】
使用循环输出1~5,当值为3时,使用yield()方法阻塞线程。
【程序代码】 
//Worker.java 
package book.ch3.yield; 
//扩展Thread 创建类Worker 
public class Worker extends Thread { 
public void run() { 
System.out .println(this.getName() + "开始执行"); 
for (int i = 1; i <= 5; i++) { 
//当输出到3 时,调用线程yield 方法 
if (i == 3) { 
Thread.yield (); 
System.out .println(this.getName() + "让出了CPU 的使用权"); 
} 
System.out .println(this.getName() + "正在输出" + i);

第3章 线程的管理 51 
} 
System.out .println(this.getName() + "结束"); 
} 
} //Index.java 
package book.ch3.yield; 
public class Index { 
public static void main(String[] args) { 
//创建两个线程并启动 
Thread t1 = new Worker(); 
Thread t2 = new Worker(); 
t1.start(); 
t2.start(); 
} 
}
【运行结果】
程序运行结果如图3-5所示。
图3-5 运行结果
【相关讨论】
从运行结果可以看出,在输出2以后,线程1和线程2分别让出了CPU 的使用权,但在下次执行
时又获取了CPU 的使用权,然后继续执行。

52 并行编程
3.3 守护线程
前面使用的线程一般称为用户线程(userthread),Java中还有一类特殊的线程,称为守护线程
(daemonthread)。在Java虚拟机中,守护线程的典型例子是垃圾收集器(garbagecollector,GC)。
守护线程与其他线程没有太大的不同,它的唯一作用是为用户线程提供服务。当只剩下守护线程
时,虚拟机会退出,这主要是因为没有可服务的线程,守护线程的运行就没有必要了。
在守护线程中,通常设定了一个无限的循环,用于等待服务的请求或完成某个任务,守护线程一般
不会承担重要的任务,这主要是因为一方面守护线程具有较低的优先级,不确定守护线程在什么时候
可以获得CPU 的时间片,另一方面不确定守护线程在什么时候结束运行。
可以将一个线程变为守护线程,方法是设置线程的属性方法setDaemon(),该方法定义的形式
如下: 
public final void setDaemon(boolean on) 
在将线程设置为守护线程时,可能会抛出异常IllegalThreadStateException和SecurityException, 
如果当前线程是活动的(alive),也就是说,该线程已经通过方法start()等途径启动,则抛出异常
IllegalThreadStateException。在设置为守护线程时,会通过方法checkAccess()判断当前线程是否可
以被修改,如果不可以,则抛出异常SecurityException。
例如: 
Thread thrd = new Thread(); 
thrd.setDaemon(true); 
thrd.start(); 
如果将方法setDaemon()的参数值设置为true,则将该线程标记为守护线程,否则为用户线程。
需要注意的是,该方法必须在调用线程的start()方法之前调用,一旦线程启动,将无法修改线程的
守护状态。如果父线程是守护线程,那么子线程也将是守护线程。可以通过线程的isDaemon()方法
判断某个线程是否为守护线程。
【例3-5】 使用守护线程完成数据维护的任务,在某一时刻使用守护线程删除队尾的数据。
【解题分析】
通过方法setDaemon()设置一个守护线程,在守护线程中通过一个无限循环监控队列的数据变
化,在某一时刻删除队尾的数据。
【程序代码】 
//Worker.java 
package book.ch3.daemon; 
//引入类LinkedList 
import java.util.LinkedList; 
public class Worker extends Thread { 
//域属性list 
private LinkedList<Integer> list;

第3章 线程的管理 53 
//构造方法定义 
public Worker(LinkedList<Integer> list) { 
this.list = list; 
} 
//重写run()方法 
@Override 
public void run() { 
//循环10 次 
for (int i = 0; i < 10; i++) { 
//生成随机数据 
int newData = (int) (Math.random () * 1000); 
//向列表中添加数据 
list.addFirst(newData); 
System.out .println("新的数据" + newData 
+ "被插入列表, Size=" + list.size()); 
//休眠1s 
try { 
sleep (1000); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 
} 
} 
} //Cleaner.java 
package book.ch3.daemon; 
import java.util.LinkedList; 
public class Cleaner extends Thread { 
//域属性list 
private LinkedList<Integer> list; 
//构造方法定义 
public Cleaner(LinkedList<Integer> list) { 
//对域属性list 赋值 
this.list = list; 
//设置为守护线程 
this.setDaemon 
(true); 
} 
//重写run()方法 
@Override 
public void run() { 
while (true) { 
//5s 后开始移除数据 
try {

54 并行编程 
sleep(5000); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 
while (true) { 
if (!list.isEmpty()) { 
list.removeLast(); 
System.out.println("一个数据已经被移除。"); 
} 
} 
} 
} 
} //Index.java 
package book.ch3.daemon; 
import java.util.LinkedList; 
public class Index { 
public static void main(String[] args) { 
LinkedList<Integer> list = new LinkedList<Integer>(); 
Thread worker = new Worker(list); 
Thread cleaner = new Cleaner(list); 
worker.start(); 
cleaner.start(); 
Runtime.getRuntime().addShutdownHook(new Thread(){ 
@Override 
public void run(){ 
System.out.println("Java 虚拟机退出"); 
} 
}); 
} 
}
【程序分析】
本例包含两个线程类定义,一个为用户线程Worker,另一个为守护线程Cleaner。在构造方法中, 
通过方法setDaemon()指明该线程为守护线程,在run()方法中设置一个无限循环,用于不断对队列的
大小进行监控,当队列非空时,移除队尾的数据。
【运行结果】
程序运行结果如图3-6所示。从图中可以看出,5s后守护线程开始执行。
【相关讨论】
由上例可见,守护线程可以帮助用户线程完成一些额外的处理工作,由于它的优先级较低,因此一
般在Worker休眠时执行。
上面的程序只使用了一个用户线程,也可以使用两个及两个以上的线程,但因为共享链表,如果读
者将线程设置为多个,则应使用同步机制保护共享数据。

第3章 线程的管理 55 
图3-6 运行结果
3.4 线程分组
如果有若干正在做同一工作的线程,为了方便对这些线程进行管理,可以对这些线程进行分组,从
而把分到同一组的若干线程作为一个整体进行操作。
在线程类Thread的构造方法中,可以指明线程属于哪一个分组,形式如下: 
public Thread(ThreadGroup group, Runnable target) 
其中,参数group可以指明该线程属于哪一个线程组。
线程组代表线程的集合,使用类ThreadGroup创建。类ThreadGroup从JDK1.0开始就已经发
布,在包java.lang下,而不在包java.util.concurrent下。
类ThreadGroup常用的构造方法主要有如下两个: 
//创建一个线程组,通过参数name 指明线程组的名字,该name 的值应该是唯一的,可以和其他线程组区分
. public ThreadGroup(String name) 
//创建一个线程组,参数parent 指明了该线程组的父线程组,参数name 指明了线程组的名字
. public ThreadGroup(ThreadGroup parent, String name) 
一个线程组可以包含其他线程组,线程组之间形成了一种树状结构,除了初始创建的线程组外,其
他线程组都有一个父线程组。一个线程允许访问所属线程组的相关信息,不允许访问父线程组的信
息。类ThreadGroup的常用方法如表3-1所示。

56 并行编程
表3-1 类ThreadGroup的常用方法
方 法含 义
publicfinalStringgetName() 获取当前线程组的名字
publicfinalThreadGroupgetParent() 获取当前线程组的父线程组
publicfinalintgetMaxPriority() 获取当前线程组的最大优先权,属于该线程组的所有线程的优先权不
能大于该值
publicfinalbooleanisDaemon() 返回当前线程组是否为一个守护线程组
publicfinalvoidsetDaemon(booleandaemon) 将当前线程组设置为一个守护线程组
publicintactiveCount() 获取当前线程组及其子线程组中活动的线程数
publicintenumerate(Threadlist[]) 将此线程组及其子线程组中的所有活动线程复制到指定数组中
publicfinalvoidstop() 停止线程组中线程的执行 
下面通过例题演示类ThreadGroup的用法。
【例3-6】 使用线程组操作10个线程,需要分别创建子线程组和父线程组,并向其中添加5个线
程,通过stop()方法停止整个线程组的运行。
【解题分析】
如果想使用线程组操作线程,则需要把线程加入线程组,线程组之间可以形成树状结构,为了指明
父线程组和子线程组,需要在线程组创建时通过参数指明父线程组。
【程序代码】 
//Worker.java 
package book.ch3.ThreadGroup; 
public class Worker implements Runnable { 
//域属性counter,用于计数 
int counter = 0; 
@Override 
public void run() { 
//循环若干次,Integer.MAX_VALUE 为整数的最大值 
for(int i=0; i<Integer.MAX_VALUE; i++){ 
counter++; 
} 
System.out.println(Thread.currentThread().getName()+"已经停止执行"); 
} 
} //Index.java 
package book.ch3.ThreadGroup; 
public class Index { 
public static void main(String[] args) { 
//线程数 
int threadNum = 5; 
//线程组parentGroup

第3章 线程的管理 57 
ThreadGroup parentGroup = new ThreadGroup("父线程组"); 
//线程组parentGroup 的子线程组 
ThreadGroup childGroup = new ThreadGroup(parentGroup,"子线程组"); 
Worker worker = new Worker(); 
//向父线程组中加入线程 
Thread[] threads = new Thread[threadNum * 2]; 
for (int i = 0; i < threadNum; i++) { 
threads[i] = new Thread(childGroup, worker); 
threads[i].start(); 
} 
//向子线程组中加入线程 
System.out.println(threadNum+"个线程被加入到子线程组"); 
for (int i = 0; i < threadNum; i++) { 
threads[threadNum + i] = new Thread(parentGroup, worker); 
threads[threadNum + i].start(); 
} 
System.out.println(threadNum+"个线程被加入到父线程组"); 
//输出活动线程数 
System.out.println("在" + parentGroup.getName() 
+ "中活动线程数为: " + parentGroup.activeCount()); 
//调用stop()方法停止子线程组线程的执行 
childGroup.stop(); 
System.out.println("子线程组已经被停止"); 
//调用stop()方法停止父线程组线程的执行 
parentGroup.stop(); 
System.out.println("父线程组已经被停止"); 
} 
}
【程序分析】
程序中创建了父线程组和子线程组,为了区分父线程组和子线程组,通过继承关系表明父线程组, 
然后在创建线程时通过参数指明哪个线程放入了哪个线程组。
【运行结果】
程序运行结果如图3-7所示。
图3-7 运行结果

58 并行编程
【相关讨论】
当有多个线程时,将线程分别加入不同的线程组有利于线程的管理。
3.5 线程本地化
类ThreadLocal是一个非常有用的类,Java通过类ThreadLocal实现线程本地对象,使用类
ThreadLocal将会使变量在每个线程的私有区域内有一个拷贝(或称副本),每个线程都可以相对独立
地改变自己的副本,而不会影响其他线程的副本。值得说明的是,ThreadLocal并不表示一个线程,而
是表示线程的一个局部变量。
在类ThreadLocal的内部实现机制上,它使用一个哈希表(Hashmap)维护线程的局部变量,哈希
表中的键(key)为线程对象,值(value)对应线程的变量副本。类ThreadLocal使用原子整型变量
AtomicInteger作为哈希表的哈希码(Hashcode),原子类型保证了在多线程环境下不会导致哈希码的
混乱。类
ThreadLocal的构造方法如下: 
//用于创建一个线程本地变量
. ThreadLocal() 
类ThreadLocal提供了方法set()和get(),用于设置和读取线程的本地值。一个线程首次获取一
个线程本地对象值时将调用方法initialValue(),该方法用于对每个线程对象进行初始化。
表3-2 类ThreadLocal的常用方法
方 法说 明
Tget() 该方法返回线程本地变量的值
protectedTinitialValue() 用于设置当前线程本地变量的初始值
voidremove() 移除本地变量
voidset(Tvalue) 设置本地变量的值 
【例3-7】 定义一个类,用于给每个线程分配一个唯一的ID。
【解题分析】
ID是一个线程的标识,可以区分线程,可以使用类ThreadLocal给每个线程分配一个唯一的ID。
这里不直接使用类ThreadLocal,而是定义一个ThreadLocal类的子类,并重写方法initialValue()。
【程序代码】 
package book.ch3.local; 
public class ThreadID { 
//定义私有的静态整型变量nextID 
private static volatile int nextID = 0; 
//定义一个内部类ThreadLocalID,它从类ThreadLocal 继承,ThreadLocal 的尖括号内为Integer。
在该内部类内,重写了方法initialValue() 
private static class ThreadLocalID extends ThreadLocal<Integer> {

第3章 线程的管理 59 
protected synchronized Integer initialValue() { 
return nextID++; 
} 
} 
private static ThreadLocalID threadID = new ThreadLocalID(); 
public static int get() { 
return threadID.get(); 
} 
public static void set(int index) { 
threadID.set(index); 
} 
}
【例3-8】 使用类ThreadLocal为每个线程增加时间戳。
【解题分析】
可以为每个线程的启动记录时间情况,为此定义一个ThreadLocal实例,并重写它的initialValue() 
方法。
【程序代码】 
//Worker.java 
package book.ch3.threadlocal; 
import java.util.Date; 
public class Worker extends Thread { 
//定义时间戳,用于记录线程的创建时间 
ThreadLocal<Date> timeStamp = new ThreadLocal<Date>(){ 
protected Date initialValue(){ 
return new Date(); 
} 
}; 
@Override 
public void run(){ 
System.out.println(getName()+"线程启动于"+timeStamp.get()); 
} 
} //Index.java 
package book.ch3.threadlocal; 
public class Index { 
public static void main(String[] args){ 
Thread t1 = new Worker(); 
Thread t2 = new Worker(); 
t1.start(); 
t2.start(); 
} 
}

60 并行编程
【运行结果】
程序运行结果如图3-8所示。
图3-8 运行结果
【相关讨论】
线程本地化可以让线程拥有属于自己的资源,在Wloka等的论文Refactoringforreentrancy① 
中,通过线程本地化操作可以实现程序的可重入性重构,在他们的方法中,有时甚至不需要使用同步控
制,有兴趣的读者可以自行阅读。
3.6 线程开销问题
使用多线程编写的程序可以最大程度地发挥多核处理器的处理能力,提高硬件资源的利用率。引
入多线程在提升程序性能的同时,也会引入一些额外的性能开销,例如线程的创建和销毁、线程之间的
同步控制、线程之间的切换和调度策略都是增加这一开销的来源。有时,如果使用线程不当,不仅不会
带来性能提升,反而会使性能下降。下面通过一个例子说明这个问题。
【例3-9】 频繁创建多个线程,在创建后只做少量的工作就立即结束,观察程序的执行时间情况。
【解题分析】
创建线程,让线程做很少的工作,例如只输出一条信息,这样在创建线程之后,线程将立刻结束。
创建多个这样的线程实例,观察程序的运行时间,并与串行执行的时间进行对比。
【程序代码】 
//Printer.java 
package book.ch3.badperformance; 
//定义类Printer 
public class Printer extends Thread { 
@Override 
public void run(){ 
//输出线程正在运行的信息 
System.out.println(this.getName()+"正在运行"); 
} 
} 
① WlokaJ,SridharanM,TipF.Refactoringforreentrancy.ProceedingsoftheEuropeanSoftwareEngineeringConferenceand 
theACMSigsoftInternationalSymposiumonFoundationsofSoftwareEngineering(ESEC/FSE),2009,Amsterdam,theNetherlands, 
August.173-182.

第3章 线程的管理 61 
//Index.java 
package book.ch3.badperformance; 
public class Index { 
public static void main(String[] args) { 
//定义线程数 
int threadNum = 100; 
//开始时间 
long start1 = System.nanoTime(); 
//为了便于对线程操作,定义线程数组,并生成每一个线程对象 
Thread[] threads = new Thread[threadNum]; 
for(int i=0; i<threadNum; i++){ 
threads[i] = new Printer(); 
threads[i].start(); 
} 
//等待这些线程执行结束 
for(int i=0;i<threadNum; i++){ 
try { 
threads[i].join(); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 
} 
//多个线程处理的结束时间 
long end1 = System.nanoTime(); 
System.out.println("使用线程的执行时间为"+(end1-start1)+"纳秒"); 
//串行执行的开始时间 
long start2 = System.nanoTime(); 
for(int i=0;i<threadNum; i++){ 
System.out.println("正在输出:"+i); 
} 
//串行执行的结束时间 
long end2 = System.nanoTime(); 
System.out.println("串行的执行时间为"+(end2-start2)+"纳秒"); 
} 
}
【程序分析】
通过在程序中创建大量线程让线程执行,比较串行和并行处理的时间。
【运行结果】
程序运行结果的部分截图如图3-9所示。
【结果分析】
从程序的执行结果可以看出,使用线程的执行时间比串行的执行时间要多,这是因为在并行程序
和串行程序执行同样工作的情况下,线程的创建和启动需要耗费更多的时间,此外,线程的上下文切
换、同步、线程阻塞操作等也都会带来开销。