从mian()方法的创建说起:守护线程和非守护线程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_44082567/article/details/86157893

创建一个mian()方法做了哪些事情?

  • 创建方法
    当我们创建了一个main()方法时,JVM为我们做了哪些事情?只是简单的开辟一个mian线程,执行mian()方法中的方法体吗?
    用代码验证一下当前的线程情况
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Scanner;


public class HelloWorld {
    public static void main(String[] args) {

        System.out.println("---------------main方法执行----------------------");
        // 获取线程管理MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 无需获取同步的monitor和synchronizer信息
        // 仅获取线程和线程堆栈信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        System.out.println("-------------开始打印线程信息------------");
        for (ThreadInfo threadInfo : threadInfos) {
        // 获取线程id和线程名
            System.out.println("[" + threadInfo.getThreadId() + "]" + "------->" + threadInfo.getThreadName());
        }
        System.out.println("main方法执行完毕");
        Scanner scanner = new Scanner(System.in);
        scanner.next();
    }
}
  • 运行结果
---------------main方法执行------------------
-------------开始打印线程信息-----------------
[6]------->Monitor Ctrl-Break
[5]------->Attach Listener
[4]------->Signal Dispatcher
[3]------->Finalizer
[2]------->Reference Handler
[1]------->main
main方法执行完毕
  • 结果分析
    可见,一个简单的main()方法,JVM还为我们创建了除main线程之外的5个线程.(创建的线程数和JDK版本以及IDE有关,这里是JDK1.7,Idea)
    Monitor Ctrl-Break:使用Idea Run程序会Fork出这个线程,Eclipse不会.
    Attach Listener:负责接收到外部的命令,对该命令进行执行并把结果返回给发送者。
    Signal Dispatcher:当Attach Listener把命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。
    Finalizer:这个线程是在main线程之后创建的,其优先级为10,主要用于在垃圾收集前,调用对象的finalize()方法.有4点需要注意:

    1. 只有当开始一轮垃圾收集时,才会开始调用finalize()方法,并不是所有对象的finalize()方法都会被执行.
    2. 该线程也是daemon线程,因此如果虚拟机中没有其他非daemon线程,不管该线程有没有执行完finalize()方法,JVM也会退出.
    3. JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收.
    4. JVM为什么要单独用一个线程来执行finalize()方法呢?如果JVM的垃圾收集线程自己来做,很有可能由于在finalize()方法中误操作导致GC线程停止或不可控,这对GC线程来说是一种灾难

    Reference Handler: JVM在创建main线程后就创建Reference Handler线程,其优先级最高,为10,它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。

守护线程和非守护线程

  • 两种线程
    java有两种线程,守护线程和非守护线程(用户线程).

    • 简单来说,守护线程就像一个守护者一样,在程序运行的时候提供一些通用的服务等.并不需要我们去过多关注.e.g:JVM中的垃圾回收线程就是一个守护线程. 当所有的非守护线程结束时,JVM自动退出,并杀死所有守护线程.
    • 非守护线程,也就是用户线程.一般是由用户创建.只要有任何用户线程还在运行,JVM就不会退出.
  • 查看线程属性
    以刚才的main()方法为例,使用JDK自带的工具查看线程属性

    • jps (Java Virtual Machine Process Status Tool):用于查看JVM中的进程,命令为:jps [options] [hostid],hostid参数为空默认访问本地的虚拟机.options参数也可为空,常用参数有-l,显示全路径名的进程.
$ jps
448336 Launcher
398940
453576 Launcher
454576 HelloWorld
452796 Jps

$ jps -l
448336 org.jetbrains.jps.cmdline.Launcher
398940
451576 sun.tools.jps.Jps
453576 org.jetbrains.jps.cmdline.Launcher
454576 com.cxw.HelloWorld

找到HelloWorld对应的pid,再使用jstack工具即可查看对应线程的堆栈信息

  • jstack 是JDK中自带的查看指定线程ID的堆栈信息的工具.
// 命令:jstack pid
$ jstack 454576

控制台打印信息如下

// 线程名后带daemon的就是守护线程
"Monitor Ctrl-Break" daemon prio=6 tid=0x000000000e4b8800 nid=0x6f198 runnable [0x000000000fade000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.read(SocketInputStream.java:150)
        at java.net.SocketInputStream.read(SocketInputStream.java:121)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
        - locked <0x00000000d602bed0> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:154)
        at java.io.BufferedReader.readLine(BufferedReader.java:317)
        - locked <0x00000000d602bed0> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:382)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
// 守护线程Attach Listener
"Attach Listener" daemon prio=10 tid=0x000000000e440800 nid=0x6d85c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
// 守护线程Signal Dispatcher
"Signal Dispatcher" daemon prio=10 tid=0x000000000e43d000 nid=0x6f208 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
// 守护线程Finalizer
"Finalizer" daemon prio=8 tid=0x000000000e3b9800 nid=0x6d1d4 in Object.wait() [0x000000000f7df000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5eb57f0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
        - locked <0x00000000d5eb57f0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)
// 守护线程Reference Handler
"Reference Handler" daemon prio=10 tid=0x000000000e3b0800 nid=0x6a3a0 in Object.wait() [0x000000000f6df000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5eb5370> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:503)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
        - locked <0x00000000d5eb5370> (a java.lang.ref.Reference$Lock)
// 用户线程main
"main" prio=6 tid=0x000000000542e000 nid=0x6de30 runnable [0x000000000370f000]
   java.lang.Thread.State: RUNNABLE
        at java.io.FileInputStream.readBytes(Native Method)
        at java.io.FileInputStream.read(FileInputStream.java:242)
        at java.io.BufferedInputStream.read1(BufferedInputStream.java:273)
        at java.io.BufferedInputStream.read(BufferedInputStream.java:334)
        - locked <0x00000000d5f07f30> (a java.io.BufferedInputStream)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
        - locked <0x00000000d5fedcd8> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.Reader.read(Reader.java:100)
        at java.util.Scanner.readInput(Scanner.java:849)
        at java.util.Scanner.next(Scanner.java:1414)
        at com.cxw.HelloWorld.main(HelloWorld.java:24)

创建守护线程

  • 创建一个守护线程,直接上代码.
public class DaemonThreadDemo {

    public class MyDaemon implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程第"+i+"次执行");
            }
        }
    }

    /**
     * 创建守护线程
     * @throws IOException
     */
    @Test
    public void DaemonThreadTest() throws IOException {
        Thread thread = new Thread(new MyDaemon());
        // setDaemon(true)设置线程为守护线程
        thread.setDaemon(true);
        thread.start();
        // isDaemon()方法查看当前线程是不是守护线程
        if (thread.isDaemon()) {
            System.out.println("当前执行的是守护线程");
        } else {
            System.out.println("当前执行的是非守护线程");
        }
        System.in.read();
    }

}

执行结果

当前执行的是守护线程
线程第0次执行
线程第1次执行
线程第2次执行
线程第3次执行
线程第4次执行

在线程执行前,使用setDaemon()方法,将线程设置为守护线程.

  • 守护线程创建的线程还是守护线程
public class DaemonThreadDemo {

    public class MyDaemon implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程第" + i + "次执行");
            }
            Thread subThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    // 守护线程中fork出的子线程依然是守护线程
                    System.out.println("子线程执行,当前线程是否守护线程" + Thread.currentThread().isDaemon());
                }
            });
            subThread.start();
        }
    }

    /**
     * 创建守护线程
     *
     * @throws IOException
     */
    @Test
    public void DaemonThreadTest() throws IOException {
        Thread thread = new Thread(new MyDaemon());
        // setDaemon(true)设置线程为守护线程
        thread.setDaemon(true);
        thread.start();
        // isDaemon()方法查看当前线程是不是守护线程
        if (thread.isDaemon()) {
            System.out.println("当前执行的是守护线程");
        } else {
            System.out.println("当前执行的是非守护线程");
        }
        System.in.read();
    }

}

执行结果

当前执行的是守护线程
线程第0次执行
线程第1次执行
线程第2次执行
线程第3次执行
线程第4次执行
子线程执行,当前线程是否守护线程true

可以看到,在守护线程中fork出的子线程依然是守护线程.

  • 创建一个用户线程作对比
public class DaemonThreadDemo {

    @Test
    public void ThreadDemo() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行,当前线程是否是守护线程"+Thread.currentThread().isDaemon());
            }
        });
        thread.start();
    }
}

执行结果

线程执行,当前线程是否是守护线程false

可以明显看出,在main()方法,以及Junit的Test方法中,没有使用setDaemon()方法设置过的线程和main()方法,Test方法保持相同的线程属性,即非守护线程.
文章参考:
JVM 内部运行线程介绍

猜你喜欢

转载自blog.csdn.net/weixin_44082567/article/details/86157893