您的位置:首页 > 编程语言 > Java开发

Thread详解1:守护进程

2016-03-27 12:08 435 查看
我认为学习java的最佳资料就是两个东西,一个是其JDK文档,一个就是源码。我们先来看看JDK文档中对于Thread类的描述,下面摘取一些重要信息,然后逐个展开讲解。

(JDK文档)

线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。

当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:

调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。

非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。

创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。例如,计算大于某一规定值的质数的线程可以写成:

class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}

public void run() {
// compute primes larger than minPrime
. . .
}
}


然后,下列代码会创建并启动一个线程:

PrimeThread p = new PrimeThread(143);
p.start();


创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。采用这种风格的同一个例子如下所示:

class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}

public void run() {
// compute primes larger than minPrime
. . .
}
}


然后,下列代码会创建并启动一个线程:

PrimeRun p = new PrimeRun(143);
new Thread(p).start();


每个线程都有一个标识名,多个线程可以同名。如果线程创建时没有指定标识名,就会为其生成一个新名称。

守护进程

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。用个比较通俗的比喻,任何一个守护线程都是整个JVM中所有非守护线程的保姆:【只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;当最后一个非守护线程结束时,守护线程随着JVM一同结束工作】。Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

User Thread和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

值得一提的是,守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。下面的方法就是用来设置守护线程的。

Thread daemonTread = new Thread();

// 设定 daemonThread 为 守护线程,default false(非守护线程)
daemonThread.setDaemon(true);

// 验证当前线程是否为守护线程,返回 true 则为守护线程
daemonThread.isDaemon();


这里有几点需要注意:

thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。

在Daemon线程中产生的新线程也是Daemon的。

不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。 因为你不可能知道在所有的User完成之前,Daemon是否已经完成了预期的服务任务。一旦User退出了,可能大量数据还没有来得及读入或写出,计算任务也可能多次运行结果不一样。这对程序是毁灭性的。造成这个结果理由已经说过了:一旦所有User Thread离开了,虚拟机也就退出运行了。

下面举一个将I/O 放到守护进程然后失败的例子:

//完成文件输出的守护线程任务
import java.io.*;

class TestRunnable implements Runnable{
public void run(){
try{
Thread.sleep(1000);//守护线程阻塞1秒后运行
File f=new File("daemon.txt");
FileOutputStream os=new FileOutputStream(f,true);
os.write("daemon".getBytes());
}
catch(IOException e1){
e1.printStackTrace();
}
catch(InterruptedException e2){
e2.printStackTrace();
}
}
}
public class TestDemo2{
public static void main(String[] args) throws InterruptedException
{
Runnable tr=new TestRunnable();
Thread thread=new Thread(tr);
thread.setDaemon(true); //设置守护线程
thread.start(); //开始执行分进程
}
}


运行结果:文件daemon.txt中没有”daemon”字符串。 原因也很简单,直到主线程完成,守护线程仍处于1秒的阻塞状态。这个时候主线程很快就运行完了,虚拟机退出,Daemon停止服务,输出操作自然失败了。

下面我举一个综合性的代码示例,大家注意注释:

MyUerThread.java

package easy;

// 通过继承 Thread 自定义一个线程类
public class MyUerThread extends Thread {
private int count = 0;

@Override
synchronized public void run() {
super.run();
// 注意这里,我让 MyUerThread 只打印5次,5次打印结束,这个线程就结束了
for (int i = 0; i < 5; i++) {
count++;
System.out.printf("MyUserThread 第 %d 次执行。\n", count);
try {
// 调用 Thread 的sleep方法,注意,它是类方法
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


MyDaemon.java

package easy;

// 通过实现 Runnable 接口自定义一个线程类
public class MyDaemon implements Runnable {
private int count = 0;

// 用synchronized保证run 方法线程安全
synchronized public void run() {
// 这里我让 MyDaemon 打印 100 次
for (int i = 0; i < 100; i++) {
count++;
System.out.printf("MyDaemon 第 %d 次执行。\n", count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}
}


Main.java

package easy;

public class Main {

public static void main(String[] args) {
// Thread 子类的创建步骤
MyThread userThread = new MyThread();
userThread.start();

// 通过 Runnable 定义的线程类的创建步骤
MyDaemon myDaemon = new MyDaemon();
Thread daemon = new Thread(myDaemon);
daemon.setDaemon(true);
daemon.start();
}
}


输出

MyUserThread 第 1 次执行。
MyDaemon 第 1 次执行。
MyDaemon 第 2 次执行。
MyUserThread 第 2 次执行。
MyUserThread 第 3 次执行。
MyDaemon 第 3 次执行。
MyDaemon 第 4 次执行。
MyUserThread 第 4 次执行。
MyUserThread 第 5 次执行。
MyDaemon 第 5 次执行。
MyDaemon 第 6 次执行。


我们发现,MyUserThread保证全部5次打印全部执行,但是 MyDaemon 原计划打印100次的,却只打印了6次。这就如同上面介绍的,用户进程全部结束,守护进程也就没有必要继续存在了,它就在合适的时间结束了,而不管内部任务是否已经完全。

总结

本文介绍了线程类的定义和创建过程,然后着重分析了一下Java中的“守护线程”又或者称之为“后天线程”的特点。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息