Java守护线程创建与运行示例:自动清理过期事件
在Java并发编程中,守护线程主要用于为其他线程提供辅助服务。本文将通过一个具体的队列清理示例,演示如何创建守护线程、其与用户线程的生命周期关联,以及在程序结束时的自动终止行为。
结果或目标
本示例旨在构建一个模拟系统:多个“用户线程”不断向双端队列添加带时间戳的事件,而一个“守护线程”在后台持续监控队列,自动删除创建时间超过10秒的过期事件。通过观察控制台输出,可以验证守护线程如何在不阻塞主程序退出的前提下执行维护任务。
准备条件
确保开发环境已配置Java JDK(示例基于Java 7+语法,但核心API兼容更高版本)。主要涉及的类包括:Event(数据结构)、WriterTask(生产者)、CleanerTask(守护消费者)和Main(启动器)。
真实操作顺序
第1步:定义事件数据结构
目的:创建用于存储事件信息的基础类。
动作:声明Date类型的日期属性和String类型的事件描述属性,并提供对应的Getter/Setter方法。
代码实现:
package com.diguage.books.concurrencycookbook.chapter1.recipe7;
import java.util.Date;
/**
* 事件信息类
*/
public class Event {
private Date date;
private String event;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getEvent() {
return event;
}
public void setEvent(String event) {
this.event = event;
}
}
第2步:编写用户线程(事件写入者)
目的:创建一个普通线程(非守护),负责周期性地向队列添加事件。
入口:实现Runnable接口。
重要参数:循环100次,每次间隔1秒。
动作:
package com.diguage.books.concurrencycookbook.chapter1.recipe7;
import java.util.Date;
import java.util.Deque;
import java.util.concurrent.TimeUnit;
public class WriterTask implements Runnable {
private Deque<Event> deque;
public WriterTask(Deque<Event> deque) {
this.deque = deque;
}
@Override
public void run() {
for (int i = 0; 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();
}
}
}
}
第3步:构建守护线程(事件清理者)
目的:创建守护线程,用于清理旧数据。
关键动作:在构造函数中调用setDaemon(true)。
注意:此方法必须在start()之前调用,否则将抛出IllegalThreadStateException。
代码实现:
package com.diguage.books.concurrencycookbook.chapter1.recipe7;
import java.util.Date;
import java.util.Deque;
public class CleanerTask extends Thread {
private Deque<Event> deque;
public CleanerTask(Deque<Event> deque) {
this.deque = deque;
// 设置为守护线程
setDaemon(true);
}
@Override
public void run() {
while (true) {
Date date = new Date();
clean(date);
}
}
/**
* 删除超过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();
// 如果时间差超过10000毫秒(10秒),则删除
if (difference > 10000) {
System.out.printf("Cleaner: %s\n", e.getDate());
deque.removeLast();
delete = true;
}
} while (difference > 10000);
if (delete) {
System.out.printf("Cleaner: Size of the queue: %d\n", deque.size());
}
}
}
第4步:主程序启动与调度
目的:初始化共享队列,启动3个写入线程和1个清理守护线程。
动作:
package com.diguage.books.concurrencycookbook.chapter1.recipe7;
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
public static void main(String[] args) {
Deque<Event> deque = new ArrayDeque<>();
WriterTask writer = new WriterTask(deque);
// 启动3个用户线程
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(writer);
thread.start();
}
// 启动1个守护线程
CleanerTask cleaner = new CleanerTask(deque);
cleaner.start();
}
}
完成判断
执行程序后,控制台应开始输出类似以下内容: - 每隔约3秒,清理线程打印出被删除事件的时间戳。 - 同时打印当前队列剩余大小(通常在27到30之间波动)。 - 当所有3个写入线程结束后(约100秒后),即使清理线程仍在运行,JVM也会因为不存在任何用户线程而立即退出,这体现了守护线程的非阻塞性特征。
原文有依据的排错
- 异常处理:如果在
CleanerTask的run()中抛出未捕获异常,由于它是守护线程,它可能直接终止而不会触发全局异常处理器。建议在clean()内部使用try-catch保护逻辑。 - 状态修改限制:牢记
setDaemon()只能在线程处于NEW状态时调用。一旦start()被调用,再次调用该方法会导致运行时异常。 - 并发安全:本示例中的
ArrayDeque并非线程安全容器。在生产环境中,若多个线程同时读写,应考虑使用ConcurrentLinkedDeque或加锁机制,以避免ConcurrentModificationException。
简要总结
守护线程是Java线程模型中的重要组成部分,专为后台辅助任务设计。通过上述示例,我们学习了如何使用setDaemon(true)创建守护线程,以及它们如何在所有用户线程结束后自动死亡。这种机制常用于垃圾回收、心跳检测等不需要保证完成且不应阻止程序退出的场景。以上就是Java守护线程创建与运行示例的详细内容,更多关于Java并发编程的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









