JDK 源码阅读006:Thread
1 类继承体系
Runnable 接口只有一个核心方法:
public abstract void run();
子类通过继承 Thread 并重写 run 方法实现自己的业务逻辑。
2 初始化
当我们使用 new Thread()
的方法创建新的线程时,会调用init
方法:
- ThreadGroup g:线程组,如果没有指定线程组,那么默认的线程组就是父线程的线程组,比如说如果你的父线程是main线程的话,那么你的线程组就是main线程的线程组(main线程组)。
- Runnable target:目标
- name:线程名字
- stackSize:默认是0。它的作用是控制jvm给线程分配栈内存的大小。如果这个值比JVM规定的最小值还小的话,取JVM的默认值,一般是1M。
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
默认情况下,如果你没有指定你是否为daemon的话,那么你的daemon的状态是由父线程决定的,就是说如果你的父线程是daemon线程,那么你也是daemon线程;同理,你的优先级如果没有指定的话,那么就跟父线程的优先级保持一致。
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) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
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;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
// 每个线程其实都有一个线程id,threadId,第一个分配的线程,它的id是1,之后的线程是2,3,4,5,依次分配各个线程的id
tid = nextThreadID();
}
3 初始化总结
-
创建 A 线程的线程,就是 A 线程的父线程
-
如果没有指定ThreadGroup,A 线程的ThreadGroup就是父线程的ThreadGroup
-
A 线程的daemon状态默认是父线程的daemon状态
-
A 线程的优先级默认是父线程的优先级
-
如果没有指定线程的名称,那么默认就是 Thread-0 格式的名称
-
线程id是全局递增的,从 1 开始
4 线程启动
看一下 start() 方法的源码:
public synchronized void start() {
// 状态不为 0(对应着 NEW 状态),则抛出异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 将线程加入线程组
group.add(this);
boolean started = false;
try {
start0();
// 启动成功,标记为启动成功
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
4.1 多次调用 start()
永远都不能对一个线程多次调用和执行start()方法:
if (threadStatus != 0)
throw new IllegalThreadStateException();
线程一旦执行过一次以后,那么它的 threadStatus 就一定会变为非 0 的一个状态。如果是非0状态,那么再次调用 start()
方法,会抛出一个异常IllegalThreadStateException
(非法的线程状态的异常)。
4.2 启动成功
一旦是start0()
成功地启动之后,他就会去执行我们重写的run()
方法(如果传入进去的是 Runnalbe
对象,就会执行那个Runnable对象的方法)。
@Override
public void run() {
if (target != null) {
target.run();
}
}
如果采用如下方式:
new Thread(new Runnable() {
public void run() {
}
}).start();
传递一个 Runnable 对象(target),如果 target 不为 null 的话,那么此时就会执行 target 的 run
方法。反之,如果你是直接自己用Thread 类继承了一个子类的话,那么你会重写这个 run()
方法,start0()
启动线程之后,就会来执行你的run()
方法。
5 线程启动总结
-
一旦启动了线程之后,就不能再重新启动了。再次调用start()方法,会抛出异常。因为启动之后,threadStatus就会变成非0的状态。
-
启动线程之后,这个线程就会加入之前处理好的那个线程组中。
-
启动一个线程实际上走的是 native 方法,start0(),会实际的启动一个线程
-
一个线程启动之后就会执行run()方法。