《Java程序员面试秘笈》—— 1.8 守护线程的创建和运行

简介: 这个程序有3个WriterTask线程,每个线程向列队写入一个事件,然后休眠1秒钟。在第一个10秒钟内,队列中有30个事件,直到3个WriterTask都休眠后,CleanerTask才开始执行,但是它没有删除任何事件。

本节书摘来异步社区《Java 7并发编程实战手册》一书中的第1章,第1.8节,作者:【西】Javier Fernández González,更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.8 守护线程的创建和运行

Java里有一种特殊的线程叫做守护(Daemon)线程。这种线程的优先级很低,通常来说,当同一个应用程序里没有其他的线程运行的时候,守护线程才运行。当守护线程是程序中唯一运行的线程时,守护线程执行结束后,JVM也就结束了这个程序。

因为这种特性,守护线程通常被用来做为同一程序中普通线程(也称为用户线程)的服务提供者。它们通常是无限循环的,以等待服务请求或者执行线程的任务。它们不能做重要的工作,因为我们不可能知道守护线程什么时候能够获取CPU时钟,并且,在没有其他线程运行的时候,守护线程随时可能结束。一个典型的守护线程是Java的垃圾回收器(Garbage Collector)。

在本节中,我们将通过范例学到如何创建守护线程,范例程序包含两个线程;一个是用户线程,它将事件写入到一个队列中;另一个是守护线程,它将管理这个队列,如果生成的事件超过10秒钟,就会被移除。

准备工作
本节的范例是在Eclipse IDE里完成的。无论你使用Eclipse还是其他的IDE(比如NetBeans),都可以打开这个IDE并且创建一个新的Java工程。

范例实现
按照接下来的步骤实现本节的范例。

1.创建Event类。这个类只存放满足本范例需要的信息。声明两个私有属性,一个日期类型的属性date;另一个字符串型的属性event。并生成这两个属性的读写方法。

2.创建WriterTask类,用以实现Runnable接口。
``
public class WriterTask implements Runnable {``
3.声明一个存放Event对象的队列,并实现一个带参数的构造器,来初始化这个队列对象。

private Deque<Event> deque;
  public WriterTask (Deque<Event> deque){
    this.deque=deque;
  }```
4.实现线程的run()方法。它将执行100次循环。在每次循环中,都会创建一个新的Event对象,并放入到队列中,然后休眠一秒钟。

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

}
}`
5.创建CleanerTask类并继承Thread类。
``
public class CleanerTask extends Thread {``
6.声明存放Event对象的队列,并实现一个带参数的构造器,来初始化这个队列对象。同时,在这个构造器中,通过setDaemon()方法把这个线程设置为守护线程。

private Deque<Event> deque;
public CleanerTask(Deque<Event> deque) {
  this.deque = deque;
  setDaemon(true);
}```
7.实现run()方法。它将无限制的重复运行,在每次运行中,将获取当前时间,并调用clean()方法。

@Override
public void run() {
while (true) {

Date date = new Date();
clean(date);

}
}`
8.实现clean()方法。clean()将读取队列的最后一个事件对象,如果这个事件是10秒钟前创建的,就将它删除并且检查下一个。如果有事件被删除,clean()将打印出这个被删除事件的信息,也打印出队列的长度,这样,我们就可以看到程序的演化过程。

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());
   }
}```
9.现在实现主类。创建一个包含main()方法的Main类。

public class Main {
public static void main(String[] args) {`
10.创建一个队列对象Deque,用来存放事件。
``
Deque deque=new ArrayDeque();``
11.创建三个WriterTask线程和一个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();```
12.运行程序并且查看结果。

工作原理
对程序的运行输出进行分析之后,我们会发现,队列中的对象会不断增长直到30个,然后到程序结束,队列的长度维持在27~30之间。

这个程序有3个WriterTask线程,每个线程向列队写入一个事件,然后休眠1秒钟。在第一个10秒钟内,队列中有30个事件,直到3个WriterTask都休眠后,CleanerTask才开始执行,但是它没有删除任何事件。因为所有的事件都小于10秒钟。在接下来的运行中,CleanerTask每秒删除3个对象,同时WriterTask会写入3个对象,所以队列的长度一直介于27~30之间。

你可以不断调试WriterTask休眠的时间。如果使用一个更小的值,会发现CleanerTask将有更少的CPU时间,并且队列的长度将增加,因为CleanerTask没有删除对象。

更多信息
setDaemon()方法只能在start()方法被调用之前设置。一旦线程开始运行,将不能再修改守护状态。
相关文章
|
10月前
|
人工智能 Kubernetes Java
回归开源,两位 Java 和 Go 程序员分享的开源贡献指引
Higress是一个基于Istio和Envoy的云原生API网关,支持AI功能扩展。它通过Go/Rust/JS编写的Wasm插件提供可扩展架构,并包含Node和Java的console模块。Higress起源于阿里巴巴,解决了Tengine配置重载及gRPC/Dubbo负载均衡问题,现已成为阿里云API网关的基础。本文介绍Higress的基本架构、功能(如AI网关、API管理、Ingress流量网关等)、部署方式以及如何参与开源贡献。此外,还提供了有效的开源贡献指南和社区交流信息。
1144 33
|
5月前
|
Java 测试技术 API
【JUC】(1)带你重新认识进程与线程!!让你深层次了解线程运行的睡眠与打断!!
JUC是什么?你可以说它就是研究Java方面的并发过程。本篇是JUC专栏的第一章!带你了解并行与并发、线程与程序、线程的启动与休眠、打断和等待!全是干货!快快快!
833 2
|
10月前
|
Java 程序员 应用服务中间件
【高薪程序员必看】万字长文拆解Java并发编程!(2 2-2)
📌 核心痛点暴击:1️⃣ 面了8家都被问synchronized锁升级?一张图看懂偏向锁→重量级锁全过程!2️⃣ 线程池参数不会配?高并发场景下这些参数调优救了项目组命!3️⃣ volatile双重检测单例模式到底安不安全?99%人踩过的内存可见性大坑!💡 独家亮点抢先看:✅ 图解JVM内存模型(JMM)三大特性,看完再也不怕指令重排序✅ 手撕ReentrantLock源码,AQS队列同步器实现原理大揭秘✅ 全网最细线程状态转换图(附6种状态转换触发条件表)
184 0
|
10月前
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
309 0
|
10月前
|
设计模式 缓存 安全
【高薪程序员必看】万字长文拆解Java并发编程!(8):设计模式-享元模式设计指南
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的经典对象复用设计模式-享元模式,废话不多说让我们直接开始。
213 0
|
10月前
|
存储 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(7):不可变类设计指南
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中Java不可变类设计指南,废话不多说让我们直接开始。
192 0
|
10月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
388 0
|
10月前
|
存储 监控 算法
Java程序员必学:JVM架构完全解读
Java 虚拟机(JVM)是 Java 编程的核心,深入理解其架构对开发者意义重大。本文详细解读 JVM 架构,涵盖类加载器子系统、运行时数据区等核心组件,剖析类加载机制,包括加载阶段、双亲委派模型等内容。阐述内存管理原理,介绍垃圾回收算法与常见回收器,并结合案例讲解调优策略。还分享 JVM 性能瓶颈识别与调优方法,分析 Java 语言特性对性能的影响,给出数据结构选择、I/O 操作及并发同步处理的优化技巧,同时探讨 JVM 安全模型与错误处理机制,助力开发者提升编程能力与程序性能。
Java程序员必学:JVM架构完全解读
|
11月前
|
人工智能 Java 程序员
Java程序员在AI时代必会的技术:Spring AI
在AI时代,Java程序员需掌握Spring AI技术以提升竞争力。Spring AI是Spring框架在AI领域的延伸,支持自然语言处理、机器学习集成与自动化决策等场景。它简化开发流程,无缝集成Spring生态,并提供对多种AI服务(如OpenAI、阿里云通义千问)的支持。本文介绍Spring AI核心概念、应用场景及开发步骤,含代码示例,助你快速入门并构建智能化应用,把握AI时代的机遇。
2261 61
|
人工智能 Java 程序员
【AI程序员】通义灵码 AI 程序员全面上线JAVA使用体验
通过 AI 程序编写一个JAVA后台项目登陆页面
880 42