精华内容
下载资源
问答
  • 记得我在之前某一篇博文里讲到过一个案例:使用java守护线程来模拟redis缓存的过期时间设定。 然后线程这个也是老生常谈的一个问题,守护线程也不陌生,在Jvm里就有大量的守护线程使用。然后本文主要记录一下我...

    每篇一句

    一个男人可以穷,但绝对不能怂。拼你想要的,挣你没有的

    前言

    记得我在之前某一篇博文里讲到过一个案例:使用java的守护线程来模拟redis缓存的过期时间设定。

    然后线程这个也是老生常谈的一个问题,守护线程也不陌生,在Jvm里就有大量的守护线程的使用。然后本文主要记录一下我在工作中使用守护线程完成业务逻辑,忽略了一点从而导致一个线上问题,进而记录排查这个过程:

    基础知识:【小家java】Java里的进程、线程、协程 、Thread、守护线程、join线程的总结

    业务背景

    业务背景为一个供需关系模型:老师和学生

    模型相当于:老师和学生都在指定一段时间里向系统签到,然后我们应用会定期的(比如20s一次)的把学生和老师撮合在一起,然后经过我们的黑匣子算法进行供需匹配,进而组成一个班级。

    然后本场景的重点就在于这个每隔20s撮合一次。针对这个模型,我一下子想到的是两种方案:

    • 方案一:被动式。也就是借助调度系统,设置一个定时器被动的、盲目的每20s就去老师、学生签到队列里看一次。有就都拿出来然后经过算法组班即可。

    显然,该方法有它的优势,那就是简单粗暴的处理得非常的简单,也非常容易理解。但是弊端也很明显:主动扫描我们发现绝大部分请求可能都是浪费掉了的,1%利用上的可能都不到,会消耗掉系统大量的资源。比如我们只能全天扫,20s扫一次,说实话是个非常非常大的量了。
    倘若你还记录一些请求日志之类的,我相信它会对你跟踪对应系统别的功能的日志信息带来极其的不便。而且这种频率的日志输出,也给网络流量、以及磁盘IO带来了不小的负担~

    • 方案二:主动式。顾名思义,就是我有学生了主动告知你,然后你来个倒计时20s就去触发对应的组班动作即可。倘若在这20s期间有其余同学进来,那也会被一起成班嘛,这就是我们最希望的效果。

    这种方式的优势:刚好就是弥补了上一种方式的不足,不用频繁的去耗费系统资源了,处理起来也更加的优雅。但是,它的实现方式就会稍微麻烦点,有两个最大的痛点吧。
    第一:就是第一个人临界情况,需要考虑并发性。
    第二:倒计时怎么倒?然后倒计时到了又怎么样触发对应的动作呢?

    可能有人会想到,可以借助MQ中间件的延迟队列功能来做。
    答:我也想过,但奈何那时候我们的技术栈还是ActiveMQ,显然是不支持的。另外说一句:即使MQ有延迟队列这样的功能,但一般MQ的官方都说了,如果是些重要的、敏感的数据,不推荐使用。

    最终,我采用的是JDK的延迟队列+守护线程的方式去实现

    实现方式

    废话少说,show me the code(伪代码如下):

    	private static final DelayQueue<RoomDelayer> CATEGORY_QUEUE = new DelayQueue<>();
        /**
         * 给处理器赋值 启动守护线程
         */
        @PostConstruct
        public void postConstruct() {
            setOperLong = redisTemplate.opsForSet();
            valueOperLong = redisTemplate.opsForValue();
            hashOperLongInt = redisTemplate.opsForHash();
    
            //启用一个守护线程 去监听延迟队列执行任务
            Thread t = new Thread(() -> {
                while (true) {
                    RoomDelayer roomDelayer = null;
                    try {
                        roomDelayer = CATEGORY_QUEUE.take();
                    } catch (Exception e) {
                        log.error("take出错了", e);
                    }
                    if (roomDelayer == null) {
                        continue;
                    }
                    //获取到延迟的任务后  交给service去执行任务
                    Integer configType = roomDelayer.getConfigType();
                    Long courseId = roomDelayer.getCourseId();
                    Long startTime = roomDelayer.getStartTime();
                    Long lessonId = roomDelayer.getLessonId();
                    String key = genRoomCategoryStuKey(configType, courseId, startTime, lessonId);
    
                    try {
                        log.info("延迟队列[" + key + "]执行start...");
                        pullQueueDataPersistRoom(configType, courseId, startTime, lessonId);
                        log.info("延迟队列[" + key + "]执行end...");
                    } catch (Exception e) {
    
                        //处理一下,万一获取锁等待超时了,导致队列丢失的问题
                        if (e instanceof RedisLockException) { //若是获取锁超时的异常 那就放进去 等待下次继续尝试
                            long execTimeAt = addEleIntoDelayQueue(configType, courseId, startTime, lessonId);
                            log.warn("因为获取执行锁超时,补贴一个延迟队列元素,信息如下: {} {} {} {} 执行时间为:{}", configType,
                                    courseId, startTime, lessonId, DateUtils.date2Str(new Date(execTimeAt)));
                        } else {
                            log.info("延迟队列持久化的时候报错,key为 " + key, e);
                        }
                    }
    
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        log.error("sleep出错了", e);
                    }
    
                }
            });
            t.setName("daemon thread for CATEGORY_QUEUE");
            t.setDaemon(true);
            t.start();
        }
    

    咋一看,这个应该是没有问题的。但是昨晚,我们的reids出问题了,导致我经常获取不到连接,有不少的报错。

    然后到第二天,我在监控日志,发现守护线程竟然没有动静了,所以猜测是死翘翘了。

    问题查找

    找同事协助用jmx连上这台机器看看:如下图
    在这里插入图片描述
    再看另外一台(服务是好使的,守护线程正常):
    在这里插入图片描述
    发现这台机器的守护线程很正常的运行着。这符合我表面上看到的现象,那到底是怎么回事呢?导致线程就这么退出了?

    刚开始怀疑是不是可能是内存溢出、或者内存漏洞、或者线程其它原因。所以我们尝试这看了dump日志,把日志文件down下来,本地分析。
    结果为:没有找到任何名称为此的线程相关信息~

    定位到原因

    最后,我想。守护线程再怎么说也是个线程啊,如果执行过程中抛出异常,那就会退出线程了。然后我分析了日志,根据线程名去找该下城下的报错:

    grep "daemon thread for CATEGORY_QUEUE] ERROR" /opt/g
    

    果然找到了:
    在这里插入图片描述
    然后跟踪代码,本以为自己都给try住了,但是还是有一失误啊。
    在这里插入图片描述
    这段代码本来是用来做异常情况下的补偿,但是,但是在我框出来的那一句话里面还有调用redis.get()的语句,从而导致再次报错了。
    这里报错就没有任何的try了,因此就导致守护线程终止了~

    解决方案

    该解决方案也是以后各位使用守护线程一定一定要注意必须做的一个方案:最外层用try包裹住,防止里面一切可能出现但又忘记了的运行时异常发生,从而终止了守护线程。那就影响非常之大了
    伪代码如下:

     //启用一个守护线程 去监听延迟队列执行任务
    Thread t = new Thread(() -> {
        while (true) {
              try {
                 // 在这里面书写你的最核心逻辑。。。。里面抛出任何异常,我都不怕了
              } catch (Exception e) {
                  log.error("daemon thread for CATEGORY_QUEUE 执行失败", e);
              }
          }
        });
    

    总结

    没什么好总结的,一句话:当你使用守护线程去处理逻辑,而必须确保此守护线程不能退出时,请无比使用try,不允许任何异常抛出~来终止守护线程即可

    最后,贴上一份守护线程使用的注意事项,供大家参考:
    在这里插入图片描述

    关注A哥

    Author A哥(YourBatman)
    个人站点 www.yourbatman.cn
    E-mail yourbatman@qq.com
    微 信 fsx641385712
    活跃平台
    公众号 BAT的乌托邦(ID:BAT-utopia)
    知识星球 BAT的乌托邦
    每日文章推荐 每日文章推荐

    BAT的乌托邦

    展开全文
  • Java 守护线程

    2020-09-20 00:22:50
    介绍 Java 守护线程及设置方法。
    守护线程(Daemon)简介

    Java 语言中的线程分为两大类:用户线程和守护线程(后台线程,如垃圾回收线程)

    守护线程的特点:

    • 一般是死循环,所有的用户线程结束,守护线程自动结束。

    • 主线程 main 方法是一个用户线程。

    守护线程引用场景:

    • 固定时间自动备份系统,需要使用定时器,可以将定时器定义为守护线程,一直守着用户线程,每当到指定时刻就会去备份系统。如果所有用户线程都结束了,守护线程自动退出,没必要进行数据备份。

    创建守护线程
    public class ThreadDaemonDemo {
        public static void main(String[] args) {
            Thread t = new ThreadDaemon();
            t.setName("守护线程");
            t.setDaemon(true);
            t.start();
    
            // 主线程,用户线程
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + " ==> " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    class ThreadDaemon extends Thread{
    
        public void run() {
            int i = 0;
            while (true){
                System.out.println(Thread.currentThread().getName() + " ==> " + (++i));
                try{
                    Thread.sleep(1000);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 设置守护线程,使用 public final void setDaemon(boolean on) 方法,值为 true 时,为守护线程,当所有用户线程结束后,守护线程也就自动结束了。
    • 可以配合定时器设置定时任务。
    展开全文
  • 什么是线程 线程是指进程中的一个执行...非守护线程包括常规的用户线程或诸如用于处理GUI事件的事件调度线程,Java虚拟机在它所有非守护线程已经离开后自动离开。 什么是守护线程(Daemon Thread) 定义详解 守护...

    什么是线程

    线程是指进程中的一个执行流程,一个进程中可以运行多个线程。
    Java有两种Thread:

    • 守护线程Daemon(守护线程)
    • 用户线程User(非守护线程)。

    什么是非守护线程(User Thread)

    非守护线程包括常规的用户线程或诸如用于处理GUI事件的事件调度线程,Java虚拟机在它所有非守护线程已经离开后自动离开。

    什么是守护线程(Daemon Thread)

    定义详解

    守护线程则是用来服务非守护(用户)线程的
    比如Java的垃圾回收机制:GC线程。
    如果没有其他的用户线程在运行,那么此时的GC线程就没有要服务的对象了,所以就没有必要继续执行下去,JVM(虚拟机)此时也就会退出了。

    拓展

    • 一方面:操作系统里面是没有所谓的守护线程的概念,只有守护进程一说,但是Java语言机制是构建在JVM的基础之上的,所以说Java平台会把操作系统的底层给屏蔽起来,让它可以在它自己的虚拟的平台里面构造出对 自己有利的机制。
    • 另一方面:语言或者说平台的设计者多多少少是受到Unix思想的影响,而守护线程机制又是针对像JVM这样的平台,于是才有了守护线程;
    • 守护线程一般在实际的使用中较少,但并非无用。
      举几个例子:JVM的垃圾回收、内存管理等线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等。

    创建守护线程

    setDaemon方法

    调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。(守护线程与普通线程写法上基本没什么区别)

    方法名称

    public final void setDaemon( boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。

    参数

    on :如果值为 true,则将该线程标记为守护线程。

    抛出

    IllegalThreadStateException - 如果该线程处于活动状态。
    SecurityException - 如果当前线程无法修改该线程。

    注意要点

    • thread.setDaemon(true)必须在thread.start()之前,否则会出现IllegalThreadStateException异常,提示该线程正在处于活动状态。如果线程是守护线程,则isDaemon方法返回true。
    • 在Daemon线程中产生的新线程也是Daemon的。
    • 不是所有的应用都可以分配给Daemon线程来进行服务。
      比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。

    代码示例

    public  class Test {  
        public  static  void main(String[] args) {  
               Thread t1 =  new MyCommon();  
               Thread t2 =  new Thread(new MyDaemon());
               t2.setDaemon( true); //设置为守护线程  
               t2.start();  
               t1.start();  
       }  
    }  
    
    class MyCommon  extends Thread {  
        public  void run() {  
                for ( int i = 0; i < 5; i++) {  
                       System.out.println( "线程1第" + i +  "次执行!");  
                        try {  
                               Thread.sleep(7);  
                       }  catch (InterruptedException e) {  
                               e.printStackTrace();  
                       }  
               }  
       }  
    }  
    
    class MyDaemon  implements Runnable {  
        public  void run() {  
                for ( long i = 0; i < 9999999L; i++) {  
                       System.out.println( "守护线程第" + i +  "次执行!");  
                        try {  
                               Thread.sleep(7);  
                       }  catch (InterruptedException e) {  
                               e.printStackTrace();  
                       }  
               }  
       }  
    } 
    

    运行效果

    在这里插入图片描述

    从上面的运行效果可以看出:
    前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。

    守护线程与非守护线程的区别

    User Thread线程和Daemon Thread守护线程唯一的区别之处就在虚拟机的离开:
    当JVM中所有的线程都是守护线程的时候,JVM就可以退出了(如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了);如果还有一个或以上的非守护线程则不会退出。(以上是针对正常退出,调用System.exit则必定会退出)。

    举个例子:
    守护线程就像小区里的保安 ,里面各种住户(非守护线程),他们是可以同时干着各自的活儿,但是住户们要是都一个个被带走了,那么门口的保安也就没有存在的意义了。

    展开全文
  • Java守护线程

    千次阅读 2019-10-24 19:03:22
    Java中线程分两种:用户线程、守护线程守护线程:顾名思义,目的就是为了守护 用户线程,守护线程依附于用户线程,当用户线程死亡后,守护线程也就没有存在的意义了,会自行消亡。 用户线程销毁时,守护线程...

    在Java中线程分两种:用户线程、守护线程。

    守护线程:顾名思义,目的就是为了守护 用户线程,守护线程依附于用户线程,当用户线程死亡后,守护线程也就没有存在的意义了,会自行消亡。

    用户线程销毁时,守护线程一定销毁。
    守护线程销毁时,用户线程不一定销毁。

    隐藏的守护线程

    当我们运行一个简单的main方法时,JVM会自动帮我们开启一些守护线程。

    使用如下代码查看:

    public static void main(String[] args) {
    	Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
    	for (Thread thread : threadSet) {
    		System.out.println(thread.getName());
    	}
    }
    输出:
    Monitor Ctrl-Break
    Signal Dispatcher
    Finalizer
    Reference Handler
    main
    
    • Monitor Ctrl-Break:IDEA监控线程。
    • Signal Dispatcher:负责接收外部jvm命令,分发到不同模块处理命令。
    • Finalizer:负责对象销毁时运行其finalize()方法。
    • Reference Handler:主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。

    还有一个很重要的守护线程:GC线程。
    它主要负责垃圾回收,是一个很称职的守护者。

    当main线程运行结束后,守护线程就无事可做了,JVM会自动销毁它们并关闭虚拟机。

    自定义守护线程

    守护线程也是线程,和创建普通线程一样,唯一的区别是start前必须声明为守护线程。
    如下:

    thread.setDaemon(true);
    

    必须start之前声明,一旦线程启动,再声明是无效的。

    示例代码

    public class DaemonThread extends Thread{
    	int index = 0;
    
    	@Override
    	public void run() {
    		while (true) {
    			System.out.println(index++);
    			SleepUtil.sleep(300);
    		}
    	}
    
    	public static void main(String[] args) {
    		Thread thread = new DaemonThread();
    		thread.setDaemon(true);
    		thread.start();
    		SleepUtil.sleep(1000);
    		System.out.println("main thread die...");
    	}
    }
    输出:
    0
    1
    2
    3
    main thread die...
    

    在守护线程中开启的线程也是守护线程。

    Tips

    对于守护线程中的 finally 语句块,JVM也不一定会执行。

    展开全文
  • Java守护线程简介

    2017-05-12 13:25:59
    在没有去了解Java守护线程是什么之前,大家是怎么猜测它的?反正我是这样以为的: Java守护线程应该是一直运行在后台的后台线程。 结果发现我太天真了,今天简单介绍一下Java的守护线程。 Java守护线程Java中的...
  • java守护线程和用户线程

    千次阅读 2020-04-17 16:44:53
    java守护线程和用户线程 1.Java线程分类 Java分两类线程:1.用户线程 2.守护线程 2.区别 1.JVM在用户线程没有结束前,会一直和守护线程一同运行。 2.如果用户线程全部结束,那么JVM会退出,守护线程也会随之结束。 3...
  • 和组内同事分享《java虚拟机》,在讲到java虚拟机生命周期时提到java守护线程和非守护线程,有同学问守护线程和非守护线程的区别和用法,这里学习一下。 守护线程和非守护线程的概念 用户线程:非守护线程包括...
  • Java多线程03_线程状态、优先级、用户线程和守护线程 线程方法: setPriority() 更改线程优先级 static void sleep() 线程休眠 void join() 插队 static void yield() 礼让 void interrupt() 中断...
  • Java守护线程与用户线程详解

    千次阅读 2020-03-19 10:03:45
    主要介绍Java守护线程与用户线程的概念和使用方法,以及相关注意事项。
  • java守护线程

    千次阅读 2018-10-19 17:34:59
    1.所谓守护线程就是运行在程序后台的线程,程序的主线程Main(比方java程序一开始启动时创建的那个线程)不会是守护线程 2.Daemon thread在Java里面的定义是,如果虚拟机中只有Daemon thread 在运行,则虚拟机退出...
  • 守护线程通常是由虚拟机自己使用的,比如执行垃圾收集任务的线程。 但是,java程序也可以把他创建的任何线程标记为守护线程。 而java程序的初始线程-就是开始于main()的那个,是非守护线程。 只要还有任何非守护...
  • java 守护线程

    2017-09-11 19:42:06
    守护线程与普通线程的唯一区别是:当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则不会退出。(以上是针对正常退出,调用System.exit则必定会退出)
  • JAVA守护线程 非守护线程

    千次阅读 2017-02-21 10:35:19
    当非守护线程执行完jvm就退出,不管是否还有守护线程在执行。所以守护线程尽量不要执行逻辑代码,顶多执行一些可有可无的辅助性代码。什么东西作为守护线程,还是不太明确?第二篇举了一个实际例子,可以加深理解。 ...
  • Java守护线程概述

    千次阅读 2015-12-24 09:46:01
    Java的线程分为两种:User Thread(用户线程)、DaemonThread(守护线程)。 只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束是,守护线程随着JVM一同结束工作,...
  • Java守护线程(后台线程)

    千次阅读 2016-06-16 16:23:48
    Java守护线程(后台线程) java的垃圾回收线程就是一个守护线程。 当线程结束的时候守护现场也就自然会结束了,守护线程和普通线程的写法是一样的,只是在start()之前要先使用public final void setDaemon(boolean ...
  • Java守护线程与非守护线程

    千次阅读 2016-07-11 10:01:44
    - 在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的...
  • Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程...
  • Java 多线程:守护线程和非守护线程

    千次阅读 2017-01-19 22:00:01
    Java 多线程:守护线程和非守护线程

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 58,856
精华内容 23,542
关键字:

java守护线程的使用

java 订阅