Java中后台线程实例解析

 更新时间:2018年02月05日 08:40:23   作者:szu_lg  
这篇文章主要介绍了Java中后台线程实例解析,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下

本文研究的主要是Java中后台线程的相关问题,具体介绍如下。

以前从来没有听说过,java中有后台线程这种东西。一般来说,JVM(JAVA虚拟机)中一般会包括俩种线程,分别是用户线程和后台线程。所谓后台线程(daemon)线程指的是:在程序运行的时候在后台提供的一种通用的服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束的时候,也就是用户线程都结束的时候,程序也就终止了。同时,会杀死进程中的所有的后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会结束。不如执行main()的就是一个非后台线程。

基于这个特点,当虚拟机中的用户线程全部退出运行时,守护线程没有服务的对象后,JVM也就退出了。

这点JDK源码中的介绍已经说明这一点了。

* Marks this thread as either a {@linkplain #isDaemon daemon} thread
* or a user thread. The Java Virtual Machine exits when the only
* threads running are all daemon threads.

1.后台线程的启动的条件:

/*必须在启动线程之前调用SetDaemon()方法,才能把这个线程设置为后台线程。
* 在这个程序里面,当我们输入了字符串以后,那么Main线程就会停止运行了
* 那么程序中已经没有可以运行的用户的线程了。所以后台线程就会被停止了
* JVM也就会被停停止了,感兴趣的读者可以自己尝试一下*/
public class DaemonRunner implements Runnable {
	@Override
	 public void run() {
		while (true) {
			for (int i = 0; i < 3; i++) {
				System.out.println("守护线程" + i);
			}
		}
	}
	public static void main(String[] args) {
		Thread daemon = new Thread(new DaemonRunner());
		daemon.setDaemon(true);
		daemon.start();
		Scanner s = new Scanner(System.in);
		String string=s.nextLine();
		Runtime.getRuntime().addShutdownHook(new Thread(){
			@Override
			   public void run() {
				super.run();
				System.out.println("JVM退出");
				try {
					TimeUnit.MILLISECONDS.sleep(50);
				}
				catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		);
	}
}

2.在后台线程中启动的线程都属于后台线程。尽管你没有明确指明它们是后台线程,但是它们的确是后台线程。

/*可以通过调用isDaemon()方法来确定线程是否是一个后台线程。如果是一个后台线程,
* 那么它创建的任何线程都被自动设置成后台的线程
* 在这个实例中,Daemon线程被设置成了后台模式,然后派生出许多子线程,这些线程并没有被设置成
* 后台模式,不过它们的确是后台线程。接着,Daemon线程进入了无限循环,并且在循环里调用了yield方法
* 把控制权交给其它的线程或者进程*/
class Daemon implements Runnable{
	private Thread[] t = new Thread[10];
	@Override
	 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();
		}
	}
}
class DaemonSpawn implements Runnable{
	@Override
	 public void run() {
		while (true) {
			Thread.yield();
		}
	}
}
public class Daemons {
	public static void main(String[] args) {
		Thread d = new Thread(new Daemon());
		d.setDaemon(true);
		d.start();
		System.out.println("d.isDaemon()=" + d.isDaemon());
		try {
			TimeUnit.SECONDS.sleep(1);
			//让启动的后台的线程可以获得一定的执行的时间。
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

最后的执行的结果如下:

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

3.通过为Executors.newCachedThreadPool()方法指定一个ThreadFactory的对象。通过这种方法,我们也可以将
我们想要启动的线程设置为后台线程。

/*在这个例子中,对于这个静态的构造方法:Executors.newCachedThreadPool(new DaemonThreadFactory()
* 我们可以为传入一个ThreadFactory的对象,那么我们就可以通过这种方法,将我们想要启动的线程设置为后台线程
* 这是要注意的。*/
class DaemonThreadFactory implements ThreadFactory{
	@Override
	  public Thread newThread(Runnable r) {
		Thread t = new Thread(r);
		t.setDaemon(true);
		return t;
	}
}
/*在这个例子中,在Main方法中,会首先调用Main方法中的普通的方法,“ System.out.println("All dameons started");”
* 所以会首先打印这一条语句。然后在主线程休眠期间,相应的后台线程,就会获得执行的时间,最后在Main线程
* 结束了运行的时候,也就是当Main线程从休眠中恢复过来的时候,那么Main线性就会结束运行。接着,
* 那么所有的后台的线程都会停止。JVM也会停止执行。*/
public class DaemonFromFactory implements Runnable {
	@Override
	  public void run() {
		try {
			while (true) {
				TimeUnit.MILLISECONDS.sleep(100);
				System.out.println(Thread.currentThread() + " " + this);
			}
		}
		catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	}
	public static void main(String[] args) {
		ExecutorService exec = Executors.newCachedThreadPool(new DaemonThreadFactory());
		for (int i = 0; i < 10; i++) {
			exec.execute(new DaemonFromFactory());
		}
		System.out.println("All dameons started");
		try {
			TimeUnit.MILLISECONDS.sleep(500);
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

最后的输出的结果为:

All dameons started
Thread[Thread-3,5,main] Concurrency.DaemonFromFactory@56214c1
Thread[Thread-2,5,main] Concurrency.DaemonFromFactory@5724147d
Thread[Thread-0,5,main] Concurrency.DaemonFromFactory@144fe080
Thread[Thread-1,5,main] Concurrency.DaemonFromFactory@104fa29e
Thread[Thread-8,5,main] Concurrency.DaemonFromFactory@5b069a7f
Thread[Thread-9,5,main] Concurrency.DaemonFromFactory@1a7288d1
Thread[Thread-7,5,main] Concurrency.DaemonFromFactory@25144c3e
Thread[Thread-4,5,main] Concurrency.DaemonFromFactory@288523d
Thread[Thread-6,5,main] Concurrency.DaemonFromFactory@1edae2a8
Thread[Thread-5,5,main] Concurrency.DaemonFromFactory@626007aa
Thread[Thread-3,5,main] Concurrency.DaemonFromFactory@56214c1
Thread[Thread-2,5,main] Concurrency.DaemonFromFactory@5724147d
Thread[Thread-6,5,main] Concurrency.DaemonFromFactory@1edae2a8
Thread[Thread-5,5,main] Concurrency.DaemonFromFactory@626007aa
Thread[Thread-4,5,main] Concurrency.DaemonFromFactory@288523d
Thread[Thread-9,5,main] Concurrency.DaemonFromFactory@1a7288d1
Thread[Thread-7,5,main] Concurrency.DaemonFromFactory@25144c3e
Thread[Thread-8,5,main] Concurrency.DaemonFromFactory@5b069a7f
Thread[Thread-1,5,main] Concurrency.DaemonFromFactory@104fa29e
Thread[Thread-0,5,main] Concurrency.DaemonFromFactory@144fe080
Thread[Thread-2,5,main] Concurrency.DaemonFromFactory@5724147d
Thread[Thread-3,5,main] Concurrency.DaemonFromFactory@56214c1
Thread[Thread-6,5,main] Concurrency.DaemonFromFactory@1edae2a8
Thread[Thread-1,5,main] Concurrency.DaemonFromFactory@104fa29e
Thread[Thread-0,5,main] Concurrency.DaemonFromFactory@144fe080
Thread[Thread-7,5,main] Concurrency.DaemonFromFactory@25144c3e
Thread[Thread-8,5,main] Concurrency.DaemonFromFactory@5b069a7f
Thread[Thread-5,5,main] Concurrency.DaemonFromFactory@626007aa
Thread[Thread-9,5,main] Concurrency.DaemonFromFactory@1a7288d1
Thread[Thread-4,5,main] Concurrency.DaemonFromFactory@288523d
Thread[Thread-2,5,main] Concurrency.DaemonFromFactory@5724147d
Thread[Thread-3,5,main] Concurrency.DaemonFromFactory@56214c1
Thread[Thread-8,5,main] Concurrency.DaemonFromFactory@5b069a7f
Thread[Thread-7,5,main] Concurrency.DaemonFromFactory@25144c3e
Thread[Thread-4,5,main] Concurrency.DaemonFromFactory@288523d
Thread[Thread-6,5,main] Concurrency.DaemonFromFactory@1edae2a8
Thread[Thread-1,5,main] Concurrency.DaemonFromFactory@104fa29e
Thread[Thread-0,5,main] Concurrency.DaemonFromFactory@144fe080
Thread[Thread-9,5,main] Concurrency.DaemonFromFactory@1a7288d1
Thread[Thread-5,5,main] Concurrency.DaemonFromFactory@626007aa
Thread[Thread-3,5,main] Concurrency.DaemonFromFactory@56214c1
Thread[Thread-2,5,main] Concurrency.DaemonFromFactory@5724147d
Thread[Thread-8,5,main] Concurrency.DaemonFromFactory@5b069a7f

4.首先应该意识到如果在用户线程突然退出的时候,那么后台线程在不执行finally子句的情况下就会终止其run方法。

/*当你调用这个程序的时候,你将看到finally子句不会执行,但是如果你注释掉对setDaemon()的调用,你将看到
* finally 子句将会执行.
* 这种行为是正确的。即便你基于前面对finally给出的承诺,并不希望出现这种行为。但是情况就是这样
* 当最后一个非后台的线程终止的时候,后台线程就会突然的停止。因为一旦main()退出后,JVM就会立即关闭所有后台的
* 线程。因为你不能以优雅的方式来关闭后台线程,所以它们几乎不是一种好的思想。非后台的Executor通常是一种
* 更好的方式,因为Executor控制的所有的任务可以同时被关闭。*/
class ADaemon implements Runnable{
  @Override
  public void run() {
    System.out.println("Starting ADaemon");
    try {
      TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
      System.out.println("Exiting via InterruptedException");
    }finally {
      System.out.println("This should always run?");
    }
  }
}
public class DaemonsDontRunFinally {
  public static void main(String[] args) {
    Thread t = new Thread(new ADaemon());
    t.setDaemon(true);
    t.start();
  }
}

最后的输出的结果如下:

Starting ADaemon

但是如果情况变为如下的情况,输出的结果又会不一样了:

class ADaemon implements Runnable{
  @Override
  public void run() {
    System.out.println("Starting ADaemon");
    try {
      TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
      System.out.println("Exiting via InterruptedException");
    }finally {
      System.out.println("This should always run?");
    }
  }
}
public class DaemonsDontRunFinally {
  public static void main(String[] args) {
    Thread t = new Thread(new ADaemon());
    t.setDaemon(true);
    t.start();
    try {
      TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

由于主线程不是突然退出的,主线程在休眠期间,后台线程得到了执行的时间,所以最后的打印的结果为:

Starting ADaemon
This should always run?

总结

以上就是本文关于Java中后台线程实例解析的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

相关文章

  • 关于jdk8升级jdk21 cxf报错的踩坑记录

    关于jdk8升级jdk21 cxf报错的踩坑记录

    在升级Java项目时,从JDK1.8和Spring2迁移到JDK21和Spring3后,遇到了JAXBException错误,原因是从JDK11开始移除了jaxb模块,而cxf在编译动态客户端时默认classpath不包含必要的类,解决方法是在jar包同级目录创建libs并添加jakarta.xml.bind-api
    2024-10-10
  • SpringBoot统一处理功能实现的全过程

    SpringBoot统一处理功能实现的全过程

    最近在做项目时需要对异常进行全局统一处理,主要是一些分类入库以及记录日志等,下面这篇文章主要给大家介绍了关于SpringBoot统一功能处理实现的相关资料,文中通过图文以及实例代码介绍的非常详细,需要的朋友可以参考下
    2023-03-03
  • Spring Boot详解配置文件的用途与用法

    Spring Boot详解配置文件的用途与用法

    SpringBoot项目是一个标准的Maven项目,它的配置文件需要放在src/main/resources/下,其文件名必须为application,其存在两种文件形式,分别是properties和yaml(或者yml)文件
    2022-06-06
  • SpringBoot参数校验之@Valid与@Validated的用法与场景

    SpringBoot参数校验之@Valid与@Validated的用法与场景

    这篇文章主要介绍了SpringBoot参数校验的用法与场景,在实际开发中,参数校验是保证接口安全性和数据完整性的重要手段,Spring Boot提供了@Valid和@Validated两个核心注解来实现参数校验,但许多开发者对它们的区别和使用场景存在疑惑,需要的朋友可以参考下
    2025-02-02
  • SpringBoot如何使用过滤器进行XSS防御

    SpringBoot如何使用过滤器进行XSS防御

    想对全局的请求都进行XSS防御可以使用servlet中的过滤器或者spring mvc中的拦截器,下面我们就来看看如何使用servlet中的过滤器进行XSS防御吧
    2024-11-11
  • Java web.xml之contextConfigLocation作用案例详解

    Java web.xml之contextConfigLocation作用案例详解

    这篇文章主要介绍了Java web.xml之contextConfigLocation作用案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • Java数据类型Integer与int的区别详细解析

    Java数据类型Integer与int的区别详细解析

    这篇文章主要介绍了Java数据类型Integer与int的区别详细解析,Ingeter是int的包装类,int的初值为0,Ingeter的初值为null,int和integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比,需要的朋友可以参考下
    2023-12-12
  • Spring运行时手动注入bean的方法实例

    Spring运行时手动注入bean的方法实例

    spring给我们提供了IOC服务,让我们可以用注解的方式,方便的使用bean的相互引用,下面这篇文章主要给大家介绍了关于Spring运行时手动注入bean的相关资料,需要的朋友可以参考下
    2022-05-05
  • Java实现汉字转全拼音的方法总结

    Java实现汉字转全拼音的方法总结

    在软件开发中,经常会遇到需要将汉字转换成拼音的场景,比如在搜索引擎优化、数据存储、国际化等方面,Java作为一种广泛使用的编程语言,提供了多种方法来实现汉字到拼音的转换,本文将详细介绍几种常用的Java汉字转全拼音的方法,并提供具体的代码示例和步骤
    2024-12-12
  • 关于jackson序列化和feign返回值的问题

    关于jackson序列化和feign返回值的问题

    这篇文章主要介绍了关于jackson序列化和feign返回值的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03

最新评论