美文网首页Java
多线程基础(九):守护线程、yield、join及线程组

多线程基础(九):守护线程、yield、join及线程组

作者: 冬天里的懒喵 | 来源:发表于2020-09-11 20:15 被阅读0次

    [toc]

    不经意间都已经在上一篇文章中聊到ReentrantLock了,但是回头一看,关于多线程基础的内容还有很多没涉及2到,而ReentrantLock却是属于比较高级的线程应用了。今天统一回顾下这些基础的知识点。

    守护线程

    在前面《多线程基础(二): Thread源码分析》中,我们提到了诸如守护线程,join等概念,现在来看看什么是守护线程。
    在java中,线程有两种,一种是用户线程,一种是守护线程。所谓守护线程,就是在线程创建之后,启动之前,通过setDaemon将其设置为true。

    t2.setDaemon(true);
    

    这样就能将一个线程设置为守护线程。
    那么守护线程有什么作用呢,其主要目的是,当一个线程被设置为守护线程之后,jvm主线程再也不会关心这个线程的运行情况,不像用户线程,如果用户线程没有执行完,那么主线程是不会退出的,那么只要设置了守护线程,主线程再所有用户线程逻辑执行完之后就会退出。

    package com.dhb.reentrantlocktest;
    
    import java.util.concurrent.TimeUnit;
    
    public class DeamonTest {
    
        public static  int i = 0;
        public static void main(String[] args) throws InterruptedException{
    
            Thread t1 = new Thread(() -> {
                try {
                    while (true) {
                        System.out.println(i++);
                        TimeUnit.SECONDS.sleep(1);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t1.setDaemon(true);
    
            Thread t2 = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t1.setDaemon(true);
    
            t1.start();
            t2.start();
        }
    }
    
    

    看如下代码:
    T1被设置为了守护线程,每秒print一个数字,但是T2是用户线程,T2sleep10秒,在这10秒之后,T2结束了,t1也就会停止:

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    Process finished with exit code 0
    

    实际上这个功能很好理解,有点类似于操作系统中的守护进程。那么这个线程可以做什么呢?这让我想到了之前写阻塞队列的时候,加了一个监控线程,定期输出队列的大小,之后在队列退出之后,监控线程也会自动停止。

    package com.dhb.reentrantlocktest;
    
    import java.util.concurrent.TimeUnit;
    
    public class DeamonTest1 {
    
        private static final int MAX = 10;
    
        private static int count = 0;
    
        public static void main(String[] args) {
    
            Thread t1 = new Thread(() -> {
                while (count < MAX){
                     count ++;
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            Thread t2 = new Thread(() -> {
                while (true) {
                    System.out.println(count);
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            t2.setDaemon(true);
            t1.start();
            t2.start();
    
        }
    }
    
    

    正如上面代码那样,t2就是监控线程,定期将数字进行打印。而t1则是将数字累加到MAX后就会退出。

    1
    1
    3
    4
    4
    6
    7
    7
    9
    9
    10
    
    Process finished with exit code 0
    

    那么守护线程就非常适合我们在各种池中用做监控线程来使用。

    2.yield

    Thread的yield方法是一个可以将当前线程的执行权限让出的方法。调用yield之后,当前执行的线程就会从RUNNING状态变为RUNNABLE状态。我们来看如下例子:

    package com.dhb.reentrantlocktest;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class YieldTest {
    
        private static final int MAX = 20;
    
        private static volatile AtomicInteger i = new AtomicInteger(0);
    
        public static void main(String[] args) throws InterruptedException{
            Thread t1 = new Thread(() -> {
                    while (i.get()<MAX) {
                        System.out.println(Thread.currentThread().getName() + " "+(i.getAndIncrement()));
    //                  Thread.yield();
                    }
            },"T1");
    
            Thread t2 = new Thread(() -> {
                while (i.get()<MAX) {
                    System.out.println(Thread.currentThread().getName() + " "+(i.getAndIncrement()));
                }
            },"T2");
    
            t1.start();
            t2.start();
        }
    
    
    }
    
    

    首先这两个线程启动,由于T1先执行,那么T1print的数据肯定会比T2多。

    T1 0
    T2 1
    T1 2
    T1 4
    T1 5
    T1 6
    T1 7
    T2 3
    T1 8
    T1 10
    T1 11
    T1 12
    T1 13
    T2 9
    T1 14
    T1 16
    T1 17
    T1 18
    T1 19
    T2 15
    

    我们将yield打开,再看看执行结果:

    T1 0
    T2 1
    T2 3
    T1 2
    T2 4
    T2 6
    T2 7
    T1 5
    T2 8
    T2 10
    T2 11
    T2 12
    T2 13
    T2 14
    T2 15
    T2 16
    T2 17
    T2 18
    T2 19
    T1 9
    

    可以看到调用yield之后,可能会让T2的次数增多。但是需要注意的是,这个情况不是绝对的。当线程从RUNNABLE状态变为RUNNING状态的时候,这个过程并不是类似公平锁那样先进先出,于synchronized导致的从BLOCK到RUNNABLE状态一样。

    3. join

    join方法是指将运行join方法的线程使其处于WAIT状态,待被运行的线程执行完之后再通知他进入RUNNABLE状态。如下所示:

    package com.dhb.reentrantlocktest;
    
    import java.util.concurrent.TimeUnit;
    
    public class JoinTest {
    
        public static void main(String[] args) throws InterruptedException{
            Thread t1 = new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName()+" begin sleep!");
                    TimeUnit.SECONDS.sleep(10);
                    System.out.println(Thread.currentThread().getName()+" weak up!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            Thread t2 = new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName()+" begin sleep!");
                    TimeUnit.SECONDS.sleep(10);
                    System.out.println(Thread.currentThread().getName()+" weak up!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
    
            t1.start();
            t2.start();
    //      t1.join();
    //      t2.join();
            System.out.println("main exit");
        }
    }
    
    

    上述代码在没开启join的时候:

    main exit
    Thread-1 begin sleep!
    Thread-0 begin sleep!
    Thread-1 weak up!
    Thread-0 weak up!
    

    可以看到,main线程已经退出了,但是线程0和1都还在运行。
    当我们打开join之后:

    Thread-0 begin sleep!
    Thread-1 begin sleep!
    Thread-1 weak up!
    Thread-0 weak up!
    main exit
    

    如果想让两个线程串行运行:

    t1.start();
    t1.join();
    t2.start();
    t2.join();
    

    这样的执行结果:

    Thread-0 begin sleep!
    Thread-0 weak up!
    Thread-1 begin sleep!
    Thread-1 weak up!
    main exit
    

    4. 线程组

    线程组是java中的一个已经不怎么被使用的概念,线程组ThreadGroup对象,可以在new Thread的时候,将线程组传入,之后能实现对线程组的统一interrupt和stop等。但是实际上我们在工作中已经不怎么使用。因为线程组只是提供了一个比较弱的管理机制,类似于在线程中打上标记,这种控制手段比较弱。而我们实际的工作中,大多数情况下是使用的线程池。

    package com.dhb.reentrantlocktest;
    
    import java.util.concurrent.TimeUnit;
    
    public class ThreadGroupTest {
    
        public static void main(String[] args) throws InterruptedException {
            ThreadGroup g1 = new ThreadGroup("G1");
            Thread t1 = new Thread(g1,() -> {
                while (true) {
                    System.out.println("*");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        System.out.println(Thread.currentThread().getName()+" Interrupt .");
                        e.printStackTrace();
                    }
                }
            },"T1");
    
            Thread t2 = new Thread(g1,() -> {
                while (true) {
                    System.out.println("-");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        System.out.println(Thread.currentThread().getName()+" Interrupt .");
                        e.printStackTrace();
                    }
                }
            },"T2");
            t1.start();
            t2.start();
            System.out.println(g1.getName());
            g1.interrupt();
            TimeUnit.SECONDS.sleep(5);
            System.out.println(g1.activeCount());
        }
    }
    
    

    上述代码执行如下:

    G1
    -
    *
    T2 Interrupt .
    T1 Interrupt .
    *
    -
    java.lang.InterruptedException: sleep interrupted
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.dhb.reentrantlocktest.ThreadGroupTest.lambda$main$0(ThreadGroupTest.java:13)
        at java.lang.Thread.run(Thread.java:748)
    java.lang.InterruptedException: sleep interrupted
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.dhb.reentrantlocktest.ThreadGroupTest.lambda$main$1(ThreadGroupTest.java:25)
        at java.lang.Thread.run(Thread.java:748)
    *
    -
    -
    *
    -
    *
    *
    -
    2
    

    可以看到可以对线程组的线程,统一实现打断方法,本来旧版本还可以实现stop方法。但是这个方法已经在新版本中被废弃。
    此外默认情况下,Thread是使用的父类的线程组。
    我们可以看Thread中的init方法:

    private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc,
                          boolean inheritThreadLocals) {
            if (name == null) {
                throw new NullPointerException("name cannot be null");
            }
    
            this.name = name;
    
            Thread parent = currentThread();
            SecurityManager security = System.getSecurityManager();
            if (g == null) {
                /* Determine if it's an applet or not */
    
                /* If there is a security manager, ask the security manager
                   what to do. */
                if (security != null) {
                    g = security.getThreadGroup();
                }
    
                /* If the security doesn't have a strong opinion of the matter
                   use the parent thread group. */
                if (g == null) {
                    g = parent.getThreadGroup();
                }
            }
    
            /* checkAccess regardless of whether or not threadgroup is
               explicitly passed in. */
            g.checkAccess();
    
    ... ...
    

    可以看到g为null的时候,使用的是parent.getThreadGroup()。默认情况下缺省的就是父线程的Group。

    package com.dhb.reentrantlocktest;
    
    import java.util.concurrent.TimeUnit;
    
    public class GroupTest {
    
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"T1");
            t1.start();
            Thread t2 = new Thread(() -> {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"T2");
            t2.start();
            System.out.println("T1 :"+t1.getThreadGroup());
            System.out.println("T2 :"+t2.getThreadGroup());
        }
    }
    
    

    在缺省情况下,都会使用默认的父线程的组。而所有线程都是main创建,那么实际上就是main所在的线程组。
    个人觉得,线程组相对线程池来说,已经不是那么重要了。我们现在很少再用线程组来管理。而是使用线程池。
    后面会对线程池单独进行介绍。

    相关文章

      网友评论

        本文标题:多线程基础(九):守护线程、yield、join及线程组

        本文链接:https://www.haomeiwen.com/subject/dgibektx.html