1 首先我们来看Thread类的大致注释翻译如下:
Thread是程序执行中的一个线程,JVM允许程序有多个线程并发执行。
每个线程都有一个优先级,线程优先级高的先执行,每个线程都可能被标记为守护进程。在程序运行时,一个线程创建了新的Thread对象,会为新的线程对象设置优先级,其优先级会等于创建他的线程的优先级。
当JVM启动时,会创建一个非守护线程(被叫做"main"的主线程),JVM会继续执行线程直到如下情况:
-->
调用exit时,线程退出。System.exit(-1); //异常退出,0/1正常退出
除了守护线程,其他线程都挂掉了或者return或者抛异常就退出
-->
有两种方式实现Thread线程,一种是继承Thread子类,另一种是声明一个类实现Runnable
每个线程有特定标识,如果没被指定,就会分配一个默认的线程名字
2 观看前情提要:
注意点:线程组的出现时机。线程组的设计模式->组合模式(统一管理)
以 new Thread(myRunnable, "线程1").start(); 总结:
先看有没线程组,有组就赋进去,有线程名就赋值进去,优先级给进去。他的线程状态得自父亲,再看安全管理器有没限制行为,有的话就限制一下,没有就不限制。
3 代码感想:
setPriority:如果线程组不为空,设置的优先级不能大于线程组优先级getMaxPriority
nextThreadID:通过自增++threadSeqNumber获取线程名中的序列号
clone:会抛CloneNotSupportedException,Thread不支持克隆
4 构造器解析
//1 无参构造,用法:new Thread();
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
解析:调用init(),初始化group、daemon、priority、stackSize、contextClassLoader等属性,其中daemon、priority、ContextClassLoader都默认从父类线程(即创建当前线程的线程)中获取。并默认给了一个线程名 "Thread-" + nextThreadNum() ,数字自增方式显示。
//2 用法:new Thread(Runnable runnable);
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
解析:将继承了Runnable接口的对象传入init并初始化,其他与1同。
//3 用法:new Thread(group, ruunable);
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
//4 new Thread("线程名");
public Thread(String name) {
init(null, null, name, 0);
}
//4 new Thread(group, "线程名");
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
//5 new Thread(runnable, "线程名");
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
//6 new Thread(group, runnable, "线程名");
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
//7 new Thread(group, runnable, name, stackSize);
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
init(group, target, name, stackSize);
}
上面的Thread进行了多次重载,都调用了init()方法,现在讲解下init方法如下:
/**
* 初始化一个线程
* @param 一个线程组
* @param 调用run()方法的对象
* @param 新的线程名称
* @param 新线程需要的堆栈大小,或者0为忽视
* @param 通过控制上下文来继承,或可为null
**/
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(jdk1.8中的注释):
** 安全管理器允许应用程序类实现一个安全策略,允许应用程序是否在安全或敏感的环境执行
** 安全管理器包含很多以check 开头的方法,这些方法在java类库中被个钟敏感操作调用,如下示例:
** SecurityManager security = System.getSecurityManager();
** if (security != null) {
** security.check<i>XXX</i>(argument, . . . );
** }
** 安全管理器有机会阻止一个抛出异常的操作,如果操作允许,安全管理器程序就会直接返回,
** 不允许则抛SecurityException异常,这个异常唯一的意外是checkTopLevelWindow,他返回的是boolean,不会抛出异常。
** 当前安全管理器是通过System.setSecurityManager方法来设置,通过System.getSecurityManager来获得
** 以下有一个特定的方法:
** 指定一个请求是否被允许或否定,默认的继承调用如下:
** AccessController.checkPermission(perm)
** 请求允许,checkPermission就会返回空,否则抛SecurityException
** 以下是一个简单的调用
** Object context = null;
** SecurityManager sm = System.getSecurityManager();
** if (sm != null) context = sm.getSecurityContext();
** if (sm != null) sm.checkPermission(permission, context);
**/
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();
/*
* Do we have the required permissions?
*/
//安全管理器不为空时
if (security != null) {
//验证当前实例是否能在不违反安全规范的情况下被构建,子类不能覆盖安全敏感的非final方法。
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
//将ThreadGroup中的就绪线程计数器+1 nUnstartedThreads++,此时线程还没被加入ThreadGroup
g.addUnstarted();
//初始化Thread中属性 线程组、守护进程标识、优先级、
this.group = g;
//是否守护进程标识、优先级 都会继承自父线程(即创建当前线程的线程)
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
//如果安全管理器为空或允许父线程在不违反安全规范的情况下被构建,则重新获取父类加载器
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
this.target = target;
//设置优先级,如果当前设置的优先级大于线程组优先级则取当前线程组的最大优先级
/**
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
**/
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
//设置线程id,return ++threadSeqNumber;
tid = nextThreadID();
}
注:默认情况下安全管理器是关闭的,可用-Djava.security.manager 开启。
5 线程启动过程讲解
上面我们说了构造方法是如何初始化的,接下来讲解下一些方法
初始化一个线程首先要实现他内部的run方法,然后再start(),示例如下:
new Thread() {
@Override
public void run() {
//执行的操作
}
}.start();
然后点开看Thread.java的源码可看到run()方法是调了runnable接口的run(),在没传入runnable即像这种new Thread(runnable)时可像上面一样重写run()。
@Override
public void run() {
if (target != null) {
target.run();
}
}
然后再看start()线程启动方法如下:
public synchronized void start() {
......
start0();
......
}
从上面可看到一个关键的native方法start0(),这个本地方法就是调用java在执行start()启动线程去调用run()方法的关键,如下:
private native void start0();
start()调用run()的过程如下:
大致流程图为:
详细过程如下:
1 线程启动调用start()时,会调用一个native方法start0()。
public synchronized void start() {
......
start0();
......
}
private native void start0();
2 当start0被加载到jvm时,调用Thread.java类中registerNatives(该方法定义在Thread.c)进行注册该本地方方法(stop0之类的也是一样被注册)
private static native void registerNatives();
static {
registerNatives();
}
3 然后可在Thread.c中看到定义好的线程公用数据和操作,可看到start0()映射的方法是JVM_StartThread如下:
static JNINativeMethod methods[] = {
{"start0", "()V",(void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive","()Z",(void *)&JVM_IsThreadAlive},
{"suspend0","()V",(void *)&JVM_SuspendThread},
..................
}
因此java的线程调用start()实际会调到C里面的JVM_StartThread方法,然后JVM_StartThread又会调用thread_enrty如下:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
…
native_thread = new JavaThread(&thread_entry, sz);
…
static void thread_entry(JavaThread* thread, TRAPS) {
HandleMark hm(THREAD);
Handle obj(THREAD, thread->threadObj());
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,obj,
KlassHandle(THREAD,SystemDictionary::Thread_klass()),
vmSymbolHandles::run_method_name(), //重点:最终调到这个方法
vmSymbolHandles::void_method_signature(),THREAD);
}
所以可总结调用的链路如下:
Java启动一个线程即thread.start()->调用native方法start0()->通过registerNatives()注册start0()
->在Thread.c的methods找到start0映射的JVM_StartThread
->JVM_StartThread中new JavaThread
->调用thead_enrty->调vmSymbolHandles::run_method_name()
参考资料:
jdk1.8源码