Java并发编程示例(七):守护线程的创建和运行

Java有一种特殊线程,守护线程,这种线程优先级特别低,只有在同一程序中的其他线程不执行时才会执行。

由于守护线程拥有这些特性,所以,一般用为为程序中的普通线程(也称为用户线程)提供服务。它们一般会有一个无限循环,或用于等待请求服务,或用于执行任务等。它们不可以做任何重要的工作,因为我们不确定他们什么时才能分配到CPU运行时间,而且当没有其他线程执行时,它们就会自动终止。这类线程的一个典型应用就是Java的垃圾回收。

在本节示例中,我们将创建两个线程,一个是普通线程,向队列中写入事件;另外一个是守护线程,清除队列中的事件,删除存在时间超过10秒的事件。

知其然

按照如下步骤,实现示例程序。

1.创建Event类,该类仅仅用于保存程序执行所需的事件信息。声明两个属性,一个是java.util.Date类型的的date熟悉,另外一个是String类型的event属性;然后生成这两个属性的读写方法。代码如下:

复制代码 代码如下:

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.创建一个名为WriterTask的类,并且实现Runnable接口。代码如下:

复制代码 代码如下:

public class WriterTask implements Runnable {

3.声明一个用来存储事件的队列属性,实现类的构造函数,并且利用其参数来初始化队列属性。代码如下:

复制代码 代码如下:

private Deque<Event> deque;

public WriterTask(Deque<Event> deque) {
    this.deque = deque;
}

4.实现该任务的run()方法,方法中含有一个遍历100次的循环。在每次遍历中,创建一个新的Event对象,然后保存到队列中,再睡眠1秒钟。代码如下:

复制代码 代码如下:

@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();
        }
    }
}

5.创建一个名为CleanerTask的类,并继承Thread类。代码如下:

复制代码 代码如下:

public class CleanerTask extends Thread {

6.声明一个用来存储事件的队列属性,实现类的构造函数,并且利用其参数来初始化队列属性。在构造方法中,通过调用setDaemon()方法,将该线程设置为守护线程。代码如下:

复制代码 代码如下:

private Deque<Event> deque;

public CleanerTask(Deque<Event> deque) {
    this.deque = deque;
    setDaemon(true);
}

7.实现run()方法,方法体内有一个无限循环,用于获取当前时间,然后调用clearn()方法。代码如下:

复制代码 代码如下:

@Override
public void run() {
    while (true) {
        Date date = new Date();
        clean(date);
    }
}

8.实现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.getDate());
            deque.removeLast();
            delete = true;
        }
    } while (difference > 10000);

if (delete) {
        System.out.printf("Clearner: Size of the queue: %d\n", deque.size());
    }
}

9.创建程序的主类,Main类,然后实现main()方法。代码如下:

复制代码 代码如下:

public class Main {
    public static void main(String[] args) {

10.使用Deque类创建存储事件的队列。代码如下:

复制代码 代码如下:

Deque<Event> deque = new ArrayDeque<>();

11.创建并启动三个WriterTask线程和一个CleanerTask线程。代码如下:

复制代码 代码如下:

Deque<Event> deque = new ArrayDeque<>();
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();

12.执行程序,查看执行结果。

知其所以然

分析程序的执行结果就可以看出,队列先增加到30,然后就在27到30之间变化,知道程序执行结束。

程序首先从三个WriterTask线程开始执行,每个线程先队列增加一个事件,然后睡眠1秒钟。在前10秒过后,在队列中将存在三十个事件。在这10秒期间,当三个WriterTask线程都睡眠时,CleanerTask线程也会运行,但是不会删除任何事件,因为所有的事件的生成时间还不超过10秒。在前10秒过后的时间里,每秒钟三个WriterTask向队列中添加三个事件;同样,CleanerTask每秒会删除三个事件。所以,事件的数目在27到30之间徘徊。

当WriterTask线程都休眠时,我们就可以自由处理时间,这段时间让守护线程得以运行。如果将WriterTask线程的睡眠时间设置得更短一点,那么CleanerTask线程将获取更少的CPU运行时间。果真如此的话,因为CleanerTask线程一直得不到足够的运行时间不能用于删除足够的事件,队列的长度将会一直增长下去。

永无止境

仅能在调用start()方法之前,通过调用setDaemon()方法将线程设置为守护线程。一旦线程开始运行,则不能修改守护状态。

还可以使用isDaemon()来检查一个线程是否为守护线程。如果是守护线程,则返回true;如果是普通线程,则返回false。

拿来主义

本文是从 《Java 7 Concurrency Cookbook》 (D瓜哥窃译为 《Java7并发示例集》 )翻译而来,仅作为学习资料使用。没有授权,不得用于任何商业行为。

小有所成

本节所用的所有示例代码的完整版。

Event类的完整代码

复制代码 代码如下:

package com.diguage.books.concurrencycookbook.chapter1.recipe7;

import java.util.Date;

/**
 * 事件信息类
 * Date: 2013-09-19
 * Time: 22:56
 */
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;
    }
}

WriterTask类的完整代码

复制代码 代码如下:

package com.diguage.books.concurrencycookbook.chapter1.recipe7;

import java.util.Date;
import java.util.Deque;
import java.util.concurrent.TimeUnit;

/**
 * 每秒生成一个事件。
 * Date: 2013-09-19
 * Time: 22:59
 */
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();
            }
        }
    }
}

CleanerTask类的完整代码

复制代码 代码如下:

package com.diguage.books.concurrencycookbook.chapter1.recipe7;

import java.util.Date;
import java.util.Deque;

/**
 * 事件清理
 * Date: 2013-09-19
 * Time: 23:33
 */
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);
        }
    }

/**
     * 删除事件。
     *
     * @param date
     */
    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.getDate());
                deque.removeLast();
                delete = true;
            }
        } while (difference > 10000);

if (delete) {
            System.out.printf("Clearner: Size of the queue: %d\n", deque.size());
        }
    }
}

Main类的完整代码

复制代码 代码如下:

package com.diguage.books.concurrencycookbook.chapter1.recipe7;

import java.util.ArrayDeque;
import java.util.Deque;

/**
 * Date: 2013-09-19
 * Time: 23:54
 */
public class Main {
    public static void main(String[] args) {
        Deque<Event> deque = new ArrayDeque<>();
        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();
    }
}

时间: 2014-12-03

Java并发编程示例(六):等待线程执行终止

在某些场景下,我们必须等待线程执行完成才能进行下一步工作.例如,某些程序在开始执行之前,需要先初始化一些资源.这时,我们可以启动一个线程专门来做初始化任务,等到线程任务完成后,再去执行其他部分. 为此,Thread类为我们提供了join()方法.当我们使用线程对象调用此方法时,正在掉调用的线程对象将被推迟到被调用对象执行完成后再开始执行. 在本节,示例程序演示等待初始化方法完成后,再去执行其他任务. 知其然 按照下面所示步骤,完成示例程序. 1.创建一个名为DataSourcesLoader的类

Java并发编程示例(九):本地线程变量的使用

共享数据是并发程序最关键的特性之一.对于无论是继承Thread类的对象,还是实现Runnable接口的对象,这都是一个非常周重要的方面. 如果创建了一个实现Runnable接口的类的对象,并使用该对象启动了一系列的线程,则所有这些线程共享相同的属性.换句话说,如果一个线程修改了一个属性,则其余所有线程都会受此改变的影响. 有时,我们更希望能在线程内单独使用,而不和其他使用同一对象启动的线程共享.Java并发接口提供了一种很清晰的机制来满足此需求,该机制称为本地线程变量.该机制的性能也非常可观.

Java并发编程示例(十):线程组

对线程分组是Java并发API提供的一个有趣功能.我们可以将一组线程看成一个独立单元,并且可以随意操纵线程组中的线程对象.比如,可以控制一组线程来运行同样的任务,无需关心有多少线程还在运行,还可以使用一次中断调用中断所有线程的执行. Java提供了ThreadGroup类来控制一个线程组.一个线程组可以通过线程对象来创建,也可以由其他线程组来创建,生成一个树形结构的线程. 根据<Effective Java>的说明,不再建议使用ThreadGroup.建议使用Executor. --D瓜哥特此

Java并发编程示例(二):获取和设置线程信息

Thread类包含几个属性,这些属性所表示的信息能帮助我们识别线程.观察其状态.控制其优先级等.这些线程包括如下几种: ID: 该属性表示每个线程的唯一标识: Name: 该属性存储每个线程的名称: Priority: 该属性存储每个Thread对象的优先级.线程优先级分1到10十个级别,1表示最低优先级,10表示最高优先级.并不推荐修改线程的优先级,但是如果确实有这方面的需求,也可以尝试一下. Status: 该属性存储线程的状态.线程共有六种不同的状态:新建(new).运行(runnable

Java并发编程之显式锁机制详解

我们之前介绍过synchronized关键字实现程序的原子性操作,它的内部也是一种加锁和解锁机制,是一种声明式的编程方式,我们只需要对方法或者代码块进行声明,Java内部帮我们在调用方法之前和结束时加锁和解锁.而我们本篇将要介绍的显式锁是一种手动式的实现方式,程序员控制锁的具体实现,虽然现在越来越趋向于使用synchronized直接实现原子操作,但是了解了Lock接口的具体实现机制将有助于我们对synchronized的使用.本文主要涉及以下一些内容: 接口Lock的基本组成成员 可重入锁Re

Java并发编程之栅栏(CyclicBarrier)实例介绍

栅栏类似闭锁,但是它们是有区别的. 1.闭锁用来等待事件,而栅栏用于等待其他线程.什么意思呢?就是说闭锁用来等待的事件就是countDown事件,只有该countDown事件执行后所有之前在等待的线程才有可能继续执行;而栅栏没有类似countDown事件控制线程的执行,只有线程的await方法能控制等待的线程执行. 2.CyclicBarrier强调的是n个线程,大家相互等待,只要有一个没完成,所有人都得等着. 场景分析:10个人去春游,规定达到一个地点后才能继续前行.代码如下 复制代码 代码如

Java并发编程示例(五):线程休眠与恢复

有时,我们需要在指定的时间点中断正在执行的线程.比如,每分钟检查一次传感器状态的线程,其余时间,线程不需要做任何事情.在此期间,线程不需要使用计算机的任何资源.过了这段时间之后,并且当Java虚拟机调度了该线程,则该线程继续执行.为此,你可以使用Thread类的sleeep()方法.该方法以休眠的方式来推迟线程的执行,而且整数类型的参数则指明休眠的毫秒数.当调用sleep()方法,休眠时间结束后,Java虚拟机分配给线程CPU运行时间,线程就会继续执行. 另一种是用sleep()方法的方式是通过

Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁

在Java5.0之前,只有synchronized(内置锁)和volatile. Java5.0后引入了显示锁ReentrantLock. ReentrantLock概况 ReentrantLock是可重入的锁,它不同于内置锁, 它在每次使用都需要显示的加锁和解锁, 而且提供了更高级的特性:公平锁, 定时锁, 有条件锁, 可轮询锁, 可中断锁. 可以有效避免死锁的活跃性问题.ReentrantLock实现了 Lock接口: 复制代码 代码如下: public interface Lock {  

深入探究Java多线程并发编程的要点

关键字synchronized synchronized关键可以修饰函数.函数内语句.无论它加上方法还是对象上,它取得的锁都是对象,而不是把一段代码或是函数当作锁. 1,当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一段时间只能有一个线程得到执行,而另一个线程只有等当前线程执行完以后才能执行这块代码. 2,当一个线程访问object中的一个synchronized(this)同步代码块时,其它线程仍可以访问这个object中是其它非synchr

Java并发编程示例(一):线程的创建和执行

开门见山 在IT圈里,每当我们谈论并发时,必定会说起在一台计算机上同时运行的一系列线程.如果这台电脑上有多个处理器或者是一个多核处理器,那么这时是实实在在的"同时运行":但是,如果计算机只有一个单核处理器,那么这时的"同时运行"只是表象而已. 所有的现代操作系统全部支持任务的并发执行.你可以边听音乐,边上网看新闻,还不耽误首发电子邮件.我们可以说,这种并发是 进程级并发 .在进程内部,我也可以看到有许许多多的并发任务.我们把运行在一个进程里面的并发任务称 线程. 和

Java并发编程示例(三):线程中断

一个多线程的Java程序,直到所有线程执行完成,整个程序才会退出.(需要注意的是,是所有非后台线程(non-daemon thread)执行完成:如果一个线程执行了System.exit()方法,程序也会退出.)有时,你想中止一个线程的执行,例如你想退出程序,或者你想取消一个正在执行的任务等. Java提供了中断机制,可以让我们显式地中断我们想中止执行的线程.中断机制的一个特征就是我们可以检查线程是否已经被中断,进而决定是否响应中止请求.线程也可以忽略中止请求,继续执行. 在本节,我们所开发的示

Java并发编程示例(四):可控的线程中断

在上一节"线程中断"中,我们讲解了如何中断一个正在执行的线程以及为了中断线程,我们必须对Thread动点什么手脚.一般情况下,我们可以使用上一节介绍的中断机制.但是,如果线程实现了一个分配到多个方法中的复杂算法,或者方法调用中有一个递归调用,我们应该使用更好的方式来控制线程的中断.为此,Java提供了InterruptedException异常.当检测到中断请求时,可以抛出此异常,并且在run()方法中捕获. 在本节,我们将使用一个线程查找指定目录及其子目录下文件来演示通过使用Inte

Java并发编程中使用Executors类创建和管理线程的用法

1. 类 Executors Executors类可以看做一个"工具类".援引JDK1.6 API中的介绍:   此包中所定义的 Executor.ExecutorService.ScheduledExecutorService.ThreadFactory 和 Callable 类的工厂和实用方法.此类支持以下各种方法: (1)创建并返回设置有常用配置字符串的 ExecutorService 的方法. (2)创建并返回设置有常用配置字符串的 ScheduledExecutorServi