e-mail:[email protected]
在Java和Java开发框架中,我们经常会遇到一系列的默认值,尽管他们种类繁多、功能不同,但他们在程序中扮演着举足轻重,至关重要的角色。
默认值,也作缺省值,即在用户(广义用户)不传入特定参数的情况下也有一个默认的值作为该参数的值,一般具有默认值的参数都有极高的特异性,也就是说,这个参数在程序中也许起着画龙点睛的作用,研究默认值有利于我们理解程序设计理念、运行机制、功能特性等。
Thread相信大家都经常使用,不陌生,研读过阿里巴巴的《阿里巴巴Java开发手册(终极版)》的同学应该都注意到,在其规范的“”并发”编程中,规定了不能显式地创建线程,只能通过创建线程池来管理线程,其目的是利用池化思想合理利用资源,但归根结底是通过实现ThradFactory接口创建,其中的方法使用了显式的构造器创建线程,以下是Thread类的9个public的构造器:
//构造器1
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
//2
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
//3
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
//4
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
//5
public Thread(String name) {
init(null, null, name, 0);
}
//6
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
//7
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
//8
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
//9
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
从上面的构造器我们不难看出,无论是有参数构造器还是无参构造器,最后都是指向init方法的,init的方法最后指向同名私有方法:
/**
* Creates a new Thread that inherits the given AccessControlContext.
* This is not a public constructor.
*/
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
从Thread构造器中可以看出,未指定stackSize的构造器的值,即默认值都是0
要理解这问题,我们需要先了解什么是Java的虚拟机栈,JVM的架构是基于虚拟机栈的,它是一种可被用来快速访问的存储空间,一般位于RAM里,其速度仅次于寄存器,栈的核心是“栈指针”,指针下移则分配新的内存,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据在多个线程或者多个栈之间是不可以共享的,但是在栈内部多个值相等的变量是可以指向一个地址的。
关于栈不理解的可以查看:https://www.tuicool.com/articles/URZrMnb
因此,这就导致了一个问题,JVM在运行java程序时,需要预先知道被存储在栈内的数据的确切大小、生命周期等信息,这时我们就说开辟了栈空间,即stack size,如果超过了栈的最大深度,就会出现StackOverflowError。
在这个内存开辟的过程中,我们不得不提到一个 名词“操作数栈”,操作数栈也常被称为操作栈,它是一个后入先出栈。同局部变量表一样,操作数栈的最大深度也是编译的时候被写入到方法表的Code属性的 max_stacks数据项中。操作数栈的每一个元素可以是任意Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位 数据类型所占的栈容量为2。栈容量的单位为“字宽”,对于32位虚拟机来说,一个”字宽“占4个字节,对于64位虚拟机来说,一个”字宽“占8个字节。
例如我们可以写一段代码来测试一下,jvm的默认栈深度:
public class TestStack{
private int counter=0;
private void recur(){
counter++;
recur();//递归
}
public void getStackDepth(){
try {
recur();
}catch (Throwable t){
System.out.println("栈最大深度:"+counter);
t.printStackTrace();
}
}
public static void main(String[] args){
TestStack stack=new TestStack();
stack.getStackDepth();
}
}
通过上面的例子我们可以看出,在实际的代码运行中,我们需要为线程开辟特定的栈空间,那Thread为什么要指定这个值为0呢?为0不就是不开辟空间了么?
/*
* The requested stack size for this thread, or 0 if the creator did
* not specify a stack size. It is up to the VM to do whatever it
* likes with this number; some VMs will ignore it.
*/
private long stackSize;
stackSize是Thread类的局部变量,官方给出的解释是:当前线程的指定栈大小,如果线程的创建者不指定大小,那默认值就是0,对这个书如何进行操作取决于JVM,有些JVM会忽略掉这个参数。
因此,设置这个参数以及这个参数是否生效取决于平台,和实际生产需要,如果设置了更小的栈深度,JVM能支持同时存活更多的线程,反之,单个线程拥有更大的递归深度,但是带来的是支持更少线程同时存活,因为我们的栈空间是一定的。
那么就有人问了,如果我故意把值设置的很高或者很低,那会怎么样呢?
事实是这样的:
如果没有特殊的需求,尽量不要修改此参数,因为它本身的作用和范围取决于平台,在不同的VM上使用此参数,跨平台迁移时,如果以前已设定了对应值,需要检查是否需要修改这个参数。
以上。