专栏首页做不甩锅的后端多线程基础(九):守护线程、yield、join及线程组

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

文章目录

不经意间都已经在上一篇文章中聊到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所在的线程组。 个人觉得,线程组相对线程池来说,已经不是那么重要了。我们现在很少再用线程组来管理。而是使用线程池。 后面会对线程池单独进行介绍。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 聊聊java中的哪些Map:(八)ConcurrentSkipListMap源码分析

    ConcurrentSkipListMap也是java并发包下面的重要容器,其类的继承结构如下:

    冬天里的懒猫
  • 在java中notify和notifyAll的区别

    notify()和notifyAll()以及wait()方法用于线程间的通信。通过调用wait()方法进入WaitSet的线程会一直处于WAITING状态,直到...

    冬天里的懒猫
  • java中的reference(四): WeakReference的应用--ThreadLocal源码分析

    看如下示例代码,我们有两个线程,a和b,线程a启动之后,sleep 2秒,从threadlocal t1中取获取person实例 p,线程b,启动之后,slee...

    冬天里的懒猫
  • Centos 下部署并优化Tomcat

    类比Windows上的tomcat 启动,通过bin目录下startup.sh脚本来启动tomcat

    缘、妙不可言
  • 数据图解:央行宣布降准降息 全球货币政策逐步分化

    中国人民银行决定,自2015年8月26日起,下调金融机构人民币贷款和存款基准利率,以进一步降低企业融资成本。其中,金融机构一年期贷款基准利率下调0.25个百分...

    灯塔大数据
  • 【小家Spring】面向切面编程之---Spring AOP的原理讲解以及源码分析(Cannot find current proxy: Set 'exposeProxy' property on )

    一说Spring AOP大家肯定不陌生,它作为Spring Framwork的两大基石之一,在Spring的产品线中有着大量的应用。相信小伙伴们在平时工作的项目...

    YourBatman
  • 【Spring】Spring MVC原理及配置详解

    Java学习123
  • java web项目启动的时候JVM_Bind,真的是tomcat端口被占用了吗?tomcat不同意了

    java web项目启动的时候,错误提示:cannot assign requested address:JVM_Bind.如下图:

    凯哥Java
  • 关于分词的一些思考

    我发现分词问题并不存在适用于所有领域的通用解决方案,之前我一直以为给词库里加一些专业词汇能够解决一些特定专业的问题,现在一想自己还是太naive了。举两个例子:...

    云时之间
  • Go 程序是怎样跑起来的

    刚开始写这篇文章的时候,目标非常大,想要探索 Go 程序的一生:编码、编译、汇编、链接、运行、退出。它的每一步具体如何进行,力图弄清 Go 程序的这一生。

    梦醒人间

扫码关注云+社区

领取腾讯云代金券