创建线程的两种方式
1.在Thread子类覆盖的run方法中编写运行代码
Thread thread1 = new Thread() {
public void run() {
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1 : " + Thread.currentThread().getName());
System.out.println("2 : " + this.getName());
}
}
};
thread1.start();
2.在传递给Thread对象的Runnable对象的run方法中编写代码
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1' : " + Thread.currentThread().getName());
// 这里为什么不能使用 this.getName();??
// 首先,这里如果使用 this 的话,代表的是一个 Runnable 接口的实现对象
// 而且上述只是实现了其run方法,并没有任何与getName方法有关的定义,如果直接调用自然会报错
// System.out.println("2' : " + this.getName());
}
}
// 加入再下面扩展了getName 方法的话,那么就可以简单的实现可以调用了
public String getName() {
return Thread.currentThread.getName();
}
});
thread2.start();
总结:总结:查看Thread类的run方法的源码,可以看到其实这两种方式都是在调用Thread对象的run方法,如果Thread类的run方法没有被覆盖,并且为该Thread对象设置了一个Runnable对象,该run方法会调用Runnable对象的run方法。
接下来我们分析一下源码:
1.首先在Thread的源码中,run方法的定义如下:
@Override
public void run() {
if (target != null) {
target.run();
}
}
在上面的源码中 target 为Thread类中的一个 Runnable 类型的局部变量,假如我们在创建一个线程的时候传入了我们重写过的 Runnable ,那么我们就会执行 Runnable 接口中的run方法的内容。
源码中的有关 Runnable 类型的 target 定义如下:
/* What will be run. */
private Runnable target;
在源码中创建线程时,传入Runnable类型的构造方法如下:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
在该构造方法中是调用了一个init方法,这里调用的init方法,是在Thread类中的一个重载的方法,这里调用的init方法的内部实现是调用了另一个init方法,而另一个init方法才是Thread类真正的init实现方法。上面调用的init方法源码:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
/* 在配置的时候传入了
该线程所属的线程组、线程的宿主对象、线程的名字、
还有线程请求的堆栈大小(每个线程都有自己的堆栈,如果这里不设置默认为0)
*/
init(g, target, name, stackSize, null, true);
}
上面init方法中调用的init方法:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
//在这个方法中增加了一个 AccessControlContext类,
//该类用于根据封装的上下文进行系统资源访问决策
boolean inheritThreadLocals) {
// 这是一个是否继承ThreadLocals的标志位
if (name == null) {
throw new NullPointerException("name cannot be null");
// 线程的名字是必须要有的,上面的init方法已经默认帮我们设置好了名字
}
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();
/*
* Do we have the required permissions?
*/
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);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID(); // 设置线程ID
}
线程的创建解决了,我们再看看线程启动的时候发生了啥?
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0) // 如果线程不是创建状态
// 抛出非法线程状态异常
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this); // 将该线程加入线程组
boolean started = false; // 设置启动标志位为false
try {
start0(); // 调用native方法,底层启动线程
started = true; // 设置启动标志位为 true
} finally {
try {
if (!started) { // 如果启动失败
group.threadStartFailed(this);// 通知该线程组线程启动失败
/*
该线程组的状态会回滚,就好像从未发生过尝试启动线程一样。
线程再次被视为线程组的未分离成员,并允许随后尝试启动该线程。
*/
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
// 线程启动的Native方法
private native void start0();
问题:如果在Thread子类覆盖的run方法中编写了运行代码,也为Thread子类对象传递了一个Runnable对象,那么线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码?
从上面的源码中我们也可以看到,Thread类的run方法是默认去判断有没有 Runnable 类的传入,但是假如子类重写了run方法之后,那么就没有了判断是否存在 Runable 类的这一个步骤,所以会执行子类的run方法的代码。