美文网首页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