创建并运行守护线程

Java有一种特殊的线程称为守护(daemon)线程。此类线程的优先级很低,通常只在同一程序中没有其他线程在运行时才运行。JVM在程序中没有非守护线程在运行、终止程序时才结束守护线程。

守护线程通常用作程序中其他普通(或称为用户)线程的服务提供者。 一般有一个无限循环等待服务请求或执行线程任务。 不能用它们执行重要的任务因为不知道它们什么时候会运行并可能在没有其他线程运行时的任意时间被结束。 一个典型的例子就是Java的垃圾收集线程。

任务

创建2个线程。一个用户线程将事件写入到一个队列;另一个守护线程负责清理事件队列,将10秒钟前生成的事件从队列中清除。

实现

本节的实现代码在 com.elanzone.books.noteeg.chpt1.sect08 package

  • 数据类 (Event)

    • 2个私有成员变量及其getter, setter方法
      • java.util.Date 类型的 date
      • String 类型的 event
  • 用户线程类 (WriterTask)

    • 使用队列来存储事件,并实现构造函数
        private Deque<Event> deque;
    
        public WriterTask(Deque<Event> deque) {
            this.deque = deque;
        }
    
    • 在 run 方法中循环 100 次,每次创建一个新事件插入到队列的 最前面 并 sleep 1 秒
        for (int i = 1; i < 100; i++) {
            String eventName = String.format("The thread %s has generated an event", Thread.currentThread().getId());
            deque.addFirst(new Event(eventName));
            System.out.printf("deque's size: %d, evnet: %s\n", deque.size(), eventName);
    
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
  • 守护线程类 (CleanerTask)

    • 扩展 Thread 类
    • 使用队列来存储事件,并实现构造函数。在构造函数中使用 setDaemon 方法标记为守护线程
        private Deque<Event> deque;
    
        public CleanerTask(Deque<Event> deque) {
            this.deque = deque;
            setDaemon(true); // 设置为守护线程
        }
    
    • 在 run 方法中有一个无限循环,在循环中获取当前实际时间并调用 clean 方法
        while (true) {
            clean(new Date());
        }
    
    • clean方法:获取 最后面 一个事件,如果该事件是创建在 10 秒前,则删除它并继续检查。
        private void clean(Date date) {
            long difference;
            boolean delete;
            if (deque.size() == 0) {
                System.out.println("Cleaner: empty deque.");
                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)

    • 使用 Deque 类创建队列以存储事件
        Deque<Event> deque = new ArrayDeque<>();
    
    • 创建并启动 3 个 WriterTask 和 1 个 CleanerTask
        WriterTask writer = new WriterTask(deque);
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(writer);
            thread.start();
        }
    
        CleanerTask cleaner = new CleanerTask(deque);
        cleaner.start();
    

工作原理

如果您分析程序执行的输出信息,您会看到队列不断增长直到有 30 个事件,然后大小在 27 和 30 个事件中变化,直到运行结束。

程序启动了 3 个 WriterTask 线程,每个线程写入 1 个事件并睡 1 秒。10 秒后,队列里有 30 个线程。 在这 10 秒中,CleanerTask 在 3 个 WriterTask 线程睡眠时执行,但是它不会删除事件,因为它们都是在 10 秒内生成的。 剩余执行过程中,CleanerTask 每秒删除 3 个事件,3 个 WriterTask 线程写入另外 3 个,所以队列中的事件数在 27 和 30 之间变化。

您可以改变 WriterTask 线程睡眠的时间。如果您使用一个较小的时间,您会看到 CleanerTask 运行时间更少,队列大小将变大,因为 CleanerTask 来不及删除事件。

了解更多

  • 您只能在调用 start 方法前调用 setDaemon 方法。线程一运行,您就不能再修改它的守护状态。
  • 您可以使用 isDaemon 方法来检查一个线程是守护线程(此方法返回 true)还是用户线程(此方法返回 false)