Java 9并发编程指南 目录
创建、运行守护线程
- 准备工作
- 实现过程
- 工作原理
- 扩展学习
因为这些特性,程序里的守护线程在正常情况下是标准线程(也称为用户)的服务提供者。它们通常包含一个无限循环,用来等待服务请求或者执行线程的任务。这种线程的典型应用是Java垃圾回收器。
在本节中,通过范例中的两个线程学习如何创建守护线程:一个用户线程以队列形式输出事件,一个守护线程清理队列,删掉10秒钟前生成的事件。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤完成范例:
- 创建Event类,用来存储范例中用到的事件信息。定义两个私有属性:一个是date,调用java.util.Date类型,另一个是event,String类型。生成两个属性的读写方法。
- 创建WriterTask类,指定其实现Runnable接口:
public class WriterTask implements Runnable {
- 定义存储事件的队列,实现初始化队列的类构造函数:
private Deque<Event> deque;
public WriterTask(Deque<Event> deque){
this.deque = deque;
}
- 实现此任务的run()方法。包含一个重复100次的循环,每次循环种,创建一个新的事件存储到队列中,然后休眠1秒钟:
@Override
public void run() {
for(int i = 1; i< 100 ; i++){
Event event = new Event();
event.setDate(new Date());
event.setEvent(String.format("The thread %s has generated an event", Thread.currentThread().getId()));
deque.addFirst(event);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 创建CleanerTask类,指定其继承Thread类:
public class CleanerTask extends Thread{
- 定义存储事件的队列,实现初始化队列的类构造函数。在构造函数种,使用setDaemon()方法设置此线程为守护线程:
private Deque<Event> deque;
public CleanerTask(Deque<Event> deque){
this.deque = deque;
setDaemon(true);
}
- 实现run()方法。包含一个无限循环,获得实际日期并且调用clean()方法:
@Override
public void run(){
while(true){
Date date = new Date();
clean(date);
}
}
- 实现clean()方法,获得队列中最后一个事件,如果此事件是在10秒前创建的,就删除它,接着检测下一个事件。如果事件被删掉了,在控制台输出此事件信息和消息队列的长度,这样就可以观察队列的变化:
private void clean(Date date){
long difference;
boolean delete;
if(deque.size() == 0){
return;
}
delete = false;
do{
Event e = deque.getLast();
difference = date.getTime() - e.getDate().getTime();
if(difference > 10000){
System.out.printf("Cleaner: %s\n", e.getEvent());
deque.removeLast();
delete = true;
}
}while(difference > 10000);
if(delete){
System.out.printf("Cleaner: Size of the queue: %d\n", deque.size());
}
}
- 现在实现主类,包含main()方法的Main类:
public class Main {
public static void main(String[] args) {
- 使用Deque类创建存储事件的队列:
Deque<Event> deque = new ConcurrentLinkedDeque<Event>();
- 创建和启动与Java虚拟机可用的处理器个数一样多的WriterTask线程,以及一个CleanerTask方法:
WriterTask writer = new WriterTask(deque);
for(int i=0; i < Runtime.getRuntime().availableProcessors(); i ++){
Thread thread = new Thread(writer);
thread.start();
}
CleanerTask cleaner = new CleanerTask(deque);
cleaner.start();
- 运行程序,查看结果。
工作原理
分析范例执行的输出结果,会考到队列逐步增加到一定长度,在测试环境中是40个事件。然后直到线程执行完成,队列长度将在40附近变化。这个长度取决于计算机的处理器个数。我在四核处理器上运行这个范例,所以加载了4个WriterTask线程。
程序启动4个WriterTask线程。每个线程输出一个事件并且休眠1秒,在运行10秒钟后,队列有40个事件。在这10秒期间,CleanerTask被执行,然而4个WriterTask线程在休眠。但是,CleanerTask不会删除任何事件,因为这些事件是在不到10秒钟之前生成的。在剩下的执行期间,CleanerTask每秒删除4个事件,然后4个WriterTask线程重新创建4个事件。所以,队列的长度在增长到40个事件之后附近变化。记住此范例的执行结果与计算机中Java虚拟机可用的核心个数有关。正常情况下,等于计算机的处理器个数。
如果使用小数值的话,会看到CleanerTask占用更少的CPU时间,同时因为CleanerTask不删除任何时间,队列长度会增加。
扩展学习
只能在调用start()方法之前调用setDaemon()方法。一旦线程处于运行状态,就无法通过调用setDaemon()方法来修改线程的守护状态。如果此时调用这个方法,会抛出IllegalThreadStateException线程。
通过使用isDaemon()方法来检测线程是否为守护线程(返回true)或者非守护线程(返回false)。