欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【java 多线程】守护线程与非守护线程的详解

程序员文章站 2023-12-10 18:08:52
java中有两类线程:user thread(用户线程)、daemon thread(守护线程) 用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用...

java中有两类线程:user thread(用户线程)、daemon thread(守护线程)

用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当vm检测仅剩一个守护线程,而用户线程都已经退出运行时,vm就会退出,因为没有如果没有了被守护这,也就没有继续运行程序的必要了。如果有非守护线程仍然存活,vm就不会退出。

守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用thread的setdaemon(true)方法设置当前线程为守护线程。

虽然守护线程可能非常有用,但必须小心确保其他所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。 因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。、

另外有几点需要注意:

  1. 1、setdaemon(true)必须在调用线程的start()方法之前设置,否则会抛出illegalthreadstateexception异常。
  2. 2、在守护线程中产生的新线程也是守护线程。
  3. 3、 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。

timer代码示例:

package day003;

import java.util.date;
import java.util.timertask;

/**
*
* 项目名称:javathread
* 类名称:mytask
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:05:28
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:05:28
* 修改备注:
* @version
*
*/
public class mytask extends timertask{

 /**
 * (non-javadoc)
 * @see java.util.timertask#run()
 */
 public void run() {
   system.out.println("任务执行了,时间为:"+new date());
 }
}
-----------------------------------------------------------------------------------
package day003;

import java.util.calendar;
import java.util.date;
import java.util.timer;

/**
*
* 项目名称:javathread
* 类名称:timertaskrun
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:08:01
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:08:01
* 修改备注:
* @version
*
*/
public class timertaskrun {
 public static void main(string[] args) {
  system.out.println("系统当前时间:"+new date());
  calendar calendar = calendar.getinstance();
  calendar.add(calendar.second, 10);
  date date = calendar.gettime();
  mytask task = new mytask();
  timer timer = new timer();
  timer.schedule(task, date);
 }
}

运行结果:

系统当前时间:mon mar 19 15:11:47 cst 2018
任务执行了,时间为:mon mar 19 15:11:57 cst 2018

 任务虽然运行完了,但进程还未销毁,呈红色状态,为什么会出现这种情况呢?

【java 多线程】守护线程与非守护线程的详解

可以看一下timer的源码

/**
  * creates a new timer. the associated thread does <i>not</i>
  * {@linkplain thread#setdaemon run as a daemon}.
  */
 public timer() {
  this("timer-" + serialnumber());
 }


 /**
  * creates a new timer whose associated thread has the specified name.
  * the associated thread does <i>not</i>
  * {@linkplain thread#setdaemon run as a daemon}.
  *
  * @param name the name of the associated thread
  * @throws nullpointerexception if {@code name} is null
  * @since 1.5
  */
 public timer(string name) {
  thread.setname(name);
  thread.start();
 }

 可以看出每创建一个timer就是启动一个新的线程,那么启动的线程不是守护线程,所以一直运行。那我们该如何将 新创建的的timer改成守护线程呢?更改如上的代码:

package day003;

import java.util.calendar;
import java.util.date;
import java.util.timer;

/**
*
* 项目名称:javathread
* 类名称:timertaskrun
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:08:01
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:08:01
* 修改备注:
* @version
*
*/
public class timertaskrun {
 public static void main(string[] args) {
  system.out.println("系统当前时间:"+new date());
  calendar calendar = calendar.getinstance();
  calendar.add(calendar.second, 10);
  date date = calendar.gettime();
  mytask task = new mytask();
  timer timer = new timer(true);
  timer.schedule(task, date);
 }
}

运行结果如下:

系统当前时间:mon mar 19 15:21:42 cst 2018

 【java 多线程】守护线程与非守护线程的详解

守护线程中产生的线程也是守护线程
如下示例:

package day003;

/**
*
* 项目名称:javathread
* 类名称:daemon
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:30:53
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:30:53
* 修改备注:
* @version
*
*/
public class daemon implements runnable {
 private thread[] t = new thread[10];

 /**
 * (non-javadoc)
 * @see java.lang.runnable#run()
 */
 public void run() {
  for (int i = 0; i < t.length; i++) {
   t[i] = new thread(new daemonspawn());
   t[i].start();
   system.out.println("daemonspawn " + i + " started.");
  }
  for (int i = 0; i < t.length; i++) {
   system.out.println("t[" + i + "].isdaemon() = " + t[i].isdaemon() + ".");
  }
  while (true) {
   thread.yield();
  }
 }
}
-----------------------------------------------------------------------------------
package day003;

/**
*
* 项目名称:javathread
* 类名称:daemonspawn
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:32:06
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:32:06
* 修改备注:
* @version
*
*/
public class daemonspawn implements runnable {

 /**
 * (non-javadoc)
 * @see java.lang.runnable#run()
 */
 public void run() {
  while (true) {
   thread.yield();
  }
 }
}
-----------------------------------------------------------------------------------
package day003;

import java.util.concurrent.timeunit;
/**
*
* 项目名称:javathread
* 类名称:daemonrun
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:36:34
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:36:34
* 修改备注:
* @version
*
*/
public class daemonrun {
 public static void main(string[] args) throws interruptedexception {
  thread d = new thread(new daemon());
  d.setdaemon(true);//必须在启动线程前调用
  d.start();
  system.out.println("d.isdaemon() = " + d.isdaemon() + ".");
  timeunit.seconds.sleep(1);
 }
}

 运行结果如图:

d.isdaemon() = true.
daemonspawn 0 started.
daemonspawn 1 started.
daemonspawn 2 started.
daemonspawn 3 started.
daemonspawn 4 started.
daemonspawn 5 started.
daemonspawn 6 started.
daemonspawn 7 started.
daemonspawn 8 started.
daemonspawn 9 started.
t[0].isdaemon() = true.
t[1].isdaemon() = true.
t[2].isdaemon() = true.
t[3].isdaemon() = true.
t[4].isdaemon() = true.
t[5].isdaemon() = true.
t[6].isdaemon() = true.
t[7].isdaemon() = true.
t[8].isdaemon() = true.
t[9].isdaemon() = true.

 如果将mian函数中的timeunit.seconds.sleep(1);注释掉,看一下timeunit.seconds.sleep()的源码:

/**
  * performs a {@link thread#sleep(long, int) thread.sleep} using
  * this time unit.
  * this is a convenience method that converts time arguments into the
  * form required by the {@code thread.sleep} method.
  *
  * @param timeout the minimum time to sleep. if less than
  * or equal to zero, do not sleep at all.
  * @throws interruptedexception if interrupted while sleeping
  */
 public void sleep(long timeout) throws interruptedexception {
  if (timeout > 0) {
   long ms = tomillis(timeout);
   int ns = excessnanos(timeout, ms);
   thread.sleep(ms, ns);
  }
 }

其实就是对thread.sleep()的封装,提供了可读性更好的线程暂停操作

注释后代码运行如下:

d.isdaemon() = true.

以上结果也说明了如果用户线程全部退出了,只剩下守护线程存在了,虚拟机也就退出了。

典型的守护线程是(gc)垃圾回收线程。

package day003;


/**
*
* 项目名称:javathread
* 类名称:mythread
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:50:12
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:50:12
* 修改备注:
* @version
*
*/
public class mythread extends thread{
 private int i = 0;
 /**
 * (non-javadoc)
 * @see java.lang.thread#run()
 */

 public void run() {
  super.run();
  try {
   while (true) {
    i++;
    system.out.println("i="+i);
    thread.sleep(1000);
   }
  } catch (exception e) {
   e.printstacktrace();
  }

 }

 public static void main(string[] args) throws interruptedexception {
  mythread daemonthread = new mythread();
  daemonthread.setdaemon(true);
  daemonthread.start();
  thread.sleep(5000);
  system.out.println("当main线程执行完毕,守护线程也停止了。");
 }
}

运行结果:

i=1
i=2
i=3
i=4
i=5
当main线程执行完毕,守护线程也停止了。

 除 jvm 内部的守护线程外,用户可以通过以下方法设置守护线程:

public final void setdaemon(boolean on)

可以通过以下方法查询线程是否为守护线程:

public final boolean isdaemon()

关于守护线程的几个要点:

1、setdaemon 方法必须在 thread.start() 之前设置,否则会抛出 java.lang.illegalthreadstateexception 异常,不能将正在运行的常规线程设置为守护线程

package day003;

/**
*
* 项目名称:javathread
* 类名称:testdaemon
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午4:01:32
* 修改人:liuc
* 修改时间:2018年3月19日 下午4:01:32
* 修改备注:
* @version
*
*/
public class testdaemon {
 public static void main(string[] args) {
  thread thread = new thread();
  thread.start();
  thread.setdaemon(true);
 }
}

运行结果:

exception in thread "main" java.lang.illegalthreadstateexception
at java.lang.thread.setdaemon(thread.java:1359)
at day003.testdaemon.main(testdaemon.java:32)

 2、不是所有的应用都可以分配给 daemon 线程来进行服务,比如读写操作或者计算逻辑,因为在 daemon 线程还没来的及进行操作时虚拟机可能已经退出了

package day003;

import java.io.file;
import java.io.fileoutputstream;
import java.io.ioexception;

/**
*
* 项目名称:javathread
* 类名称:testdaemon2
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午4:03:22
* 修改人:liuc
* 修改时间:2018年3月19日 下午4:03:22
* 修改备注:
* @version
*
*/
public class testdaemon2 extends thread{

 /**
 * (non-javadoc)
 * @see java.lang.thread#run()
 */

 public void run() {
  super.run();
  fileoutputstream outputstream = null;
  try {
   thread.sleep(3000);
   file file = new file("daemon.txt");
   outputstream = new fileoutputstream(file);
   outputstream.write("daemon".getbytes());
  } catch (interruptedexception e) {
   e.printstacktrace();
  } catch (ioexception e) {
   e.printstacktrace();
  } finally {
   if (outputstream != null) {
    try {
     outputstream.close();
    } catch (ioexception e) {
     e.printstacktrace();
    }
   }
  }
 }
 public static void main(string[] args) {
  thread thread = new testdaemon2();
  thread.setdaemon(true);
  thread.start();
 }

以上代码中线程功能是向工程根目录的“daemon.txt”文件写入字符串“daemon”,实际运行结果发现并未成功写入,且未报任何错误,原因是写入文件的线程被设置为守护线程,该线程还在 sleep 过程中时所有用户线程就全部结束了,守护线程也会随着 jvm 一起退出。

如果将上面代码中的thread.setdaemon(true);注释掉,

 public static void main(string[] args) {
  thread thread = new testdaemon2();
  //thread.setdaemon(true);
  thread.start();
 }

不将线程设置为守护线程可以在工程根目录的“daemon.txt”文件中看到字符串“daemon”

【java 多线程】守护线程与非守护线程的详解

示例2:

package day003;

/**
*
* 项目名称:javathread
* 类名称:customthread
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午4:16:42
* 修改人:liuc
* 修改时间:2018年3月19日 下午4:16:42
* 修改备注:
* @version
*
*/
public class customthread extends thread {

 /**
 * (non-javadoc)
 * @see java.lang.thread#run()
 */
 public void run() {
  super.run();
  for (int i = 0; i < 100; i++) {
   system.out.println("daemon thread : " + i);
  }
 }

 public static void main(string[] args) {
  thread daemonthread = new customthread();
  daemonthread.setdaemon(true);
  thread userthread = new thread();
  daemonthread.start();
  userthread.start();
 }
}

多次执行示例2代码,控制台要么不打印任何信息,要么打印一部分循环的输出信息就结束了,从运行结果可以看出,守护线程并未执行完成所有循环就结束了,因为用户线程在守护线程执行循环的过程中就已全部结束,守护线程也随着 jvm 一起结束。

以上所述是小编给大家介绍的java守护线程与非守护线程详解整合,希望对大家有所帮助