1

今天研究了下Java线程基础知识,发现以前太多知识知识略略带过了,比较说Java的线程机制,在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程),以及构造器中的stackSize.....

<!-- more -->

守护线程

估计学过Unix开发但是没有细致学习Java的同学们会疑惑了,操作系统里面是没有所谓的守护线程的概念,只有守护进程一说,但是Java语言机制是构建在JVM的基础之上的,意思是Java平台把操作系统的底层给屏蔽起来,所以它可以在它自己的虚拟的平台里面构造出对自己有利的机制,而语言或者说平台的设计者多多少少是受到Unix思想的影响,而守护线程机制又是对JVM这样的平台凑合,于是守护线程应运而生。

Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。

守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:setDaemon(boolean on) 但是有几点需要注意:

  • thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常,因为你不能把正在运行的常规线程设置为守护线程。(备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)
public final void setDaemon(boolean on) {
    checkAccess();
    if (isAlive()) {//如果处于运行状态,抛出异常
        throw new IllegalThreadStateException();
    }
    daemon = on;
}
  • Daemon线程中产生的新线程也是Daemon的。(这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是父进程挂掉,init收养
  • 不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。
Thread thread = new Thread(() -> {
    Thread innerThread = new Thread(() -> {
        try {
            for (int i = 1; i < 10; i++) {
                Thread.sleep(1_000);
                System.out.println("守护线程 " + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    innerThread.setDaemon(true);
    innerThread.start();
    try {
        for (int i = 1; i < 6; i++) {
            Thread.sleep(1_000);
            System.out.println("常规线程 " + i);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
thread.start();

日志分析

常规线程 1
守护线程 1
守护线程 2
常规线程 2
守护线程 3
常规线程 3
守护线程 4
常规线程 4
守护线程 5
常规线程 5

从上面的日志中可以看到,如果将innerThread设置成守护模式,那么待当前主线程完成处理退出后,守护线程也会随着销毁

JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台线程候一定要注意这个问题。

那么daemon Thread实际应用在那里呢?举个例子,Web服务器中的Servlet,容器启动时后台初始化一个服务线程,即调度线程,负责处理http请求,然后每个请求过来调度线程线程池中取出一个工作者线程来处理该请求,从而实现并发控制的目的。

微信公众号

线程函数

在学习Thread的时候,常见写法就是RunnableThreadName,所以这里重点讲解ThreadGroup以及stackSize的作用

public Thread(); 
public Thread(Runnable target); 
public Thread(String name); 
public Thread(Runnable target, String name); 
public Thread(ThreadGroup group, Runnable target); 
public Thread(ThreadGroup group, String name); 
public Thread(ThreadGroup group, Runnable target, String name); 
public Thread(ThreadGroup group, Runnable target, String name, long stackSize); 
  • 第一种是实例化一个无参构造函数,ThreadNameGroupName为系统默认
  • 第二种是实现了Runnable接口的类的实例,Thread类也实现了Runnable接口,因此,从Thread类继承的类的实例也可以作为target传入这个构造方法。
  • 第三种是可以自定义ThreadName
  • 第五种是可以指定该线程属于哪个ThreadGroup(线程组)
  • 第八种是可以指定堆栈大小(比如压栈大小),这个值一般是CPU页面的整数倍,如x86的页面大小是4KB.在x86平台下,默认的线程栈大小是12KB.

ThreadName,可以通过创建Thread实例后调用,setName方法设置。默认线程名:Thread-N,N是线程建立的顺序,是一个不重复的正整数,它的来源基于Thread.nextThreadNum()

/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

stackSize是一种具有平台依赖性的参数,它能指定堆栈的大小。 在某些平台上,指定一个较高的 stackSize 参数值可能使线程在抛出 StackOverflowError 之前达到较大的递归深度。stackSize 参数的值与最大递归深度和并发程度之间的关系细节与平台有关。在某些平台上,stackSize 参数的值无论如何不会起任何作用。作为建议,可以让虚拟机自由处理 stackSize 参数。

ThreadGroup group = new ThreadGroup("battcn-group");
Thread t1 = new Thread(() -> System.out.println("hello my name's" + Thread.currentThread().getName() + " group name's" + Thread.currentThread().getThreadGroup().getName()));
Thread t2 = new Thread(() -> System.out.println("hello my name's" + Thread.currentThread().getName() + " group name's" + Thread.currentThread().getThreadGroup().getName()), "thread-battcn2");
Thread t3 = new Thread(() -> System.out.println("hello my name's" + Thread.currentThread().getName() + " group name's" + Thread.currentThread().getThreadGroup().getName()), "thread-battcn3");
t1.start();
t2.start();
t3.start();
group.enumerate(new Thread[]{t2, t3});

以下是上部代码片段的日志输出,可以看到指定ThreadNameThreadGroup以及不指定的的区别

hello my name's Thread-0  group name'smain
hello my name's thread-battcn2  group name'smain
hello my name's thread-battcn3  group name'smain

警告 如果对stackSize 有兴趣的可以试试下面代码,不过慎重,有可能吧你电脑内存跑完....

for (int i = 0; i < Integer.MAX_VALUE; i++) {
    new Thread(group, new Runnable() {
        @Override
        public void run() {
            try {
                add(1);
            } catch (Error error) {
                System.out.println(count);
                error.printStackTrace();
            }
        }
        private void add(int i) {
            count++;
            add(i + 1);
        }
    }, "thread-battcn-4",1 << 24).start();
}

- 说点什么

微信公众号:battcn(欢迎调戏)

微信公众号

全文代码:https://git.oschina.net/battcn/battcn-concurent

  • 个人QQ:1837307557
  • battcn开源群(适合新手):3916196

峰峰峰峰峰
569 声望105 粉丝

胆小认生,不易相处