Java 9并发编程指南 目录


创建、运行守护线程

  • 准备工作
  • 实现过程
  • 工作原理
  • 扩展学习


因为这些特性,程序里的守护线程在正常情况下是标准线程(也称为用户)的服务提供者。它们通常包含一个无限循环,用来等待服务请求或者执行线程的任务。这种线程的典型应用是Java垃圾回收器。

在本节中,通过范例中的两个线程学习如何创建守护线程:一个用户线程以队列形式输出事件,一个守护线程清理队列,删掉10秒钟前生成的事件。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤完成范例:

  1. 创建Event类,用来存储范例中用到的事件信息。定义两个私有属性:一个是date,调用java.util.Date类型,另一个是event,String类型。生成两个属性的读写方法。
  2. 创建WriterTask类,指定其实现Runnable接口:
public class WriterTask implements Runnable {
  1. 定义存储事件的队列,实现初始化队列的类构造函数:
private Deque<Event> deque;
	public WriterTask(Deque<Event> deque){
		this.deque = deque;
	}
  1. 实现此任务的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();
                }
            }
        }
  1. 创建CleanerTask类,指定其继承Thread类:
public class CleanerTask extends Thread{
  1. 定义存储事件的队列,实现初始化队列的类构造函数。在构造函数种,使用setDaemon()方法设置此线程为守护线程:
private Deque<Event> deque;
	public CleanerTask(Deque<Event> deque){
		this.deque = deque;
		setDaemon(true);
	}
  1. 实现run()方法。包含一个无限循环,获得实际日期并且调用clean()方法:
@Override
	public void run(){
		while(true){
			Date date = new Date();
			clean(date);
		}
	}
  1. 实现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());
		}
	}
  1. 现在实现主类,包含main()方法的Main类:
public class Main {
	public static void main(String[] args) {
  1. 使用Deque类创建存储事件的队列:
Deque<Event> deque = new ConcurrentLinkedDeque<Event>();
  1. 创建和启动与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();
  1. 运行程序,查看结果。

工作原理

分析范例执行的输出结果,会考到队列逐步增加到一定长度,在测试环境中是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)。