在上一讲内容中,我们已经学习了实现线程的两种方式,但是到底这两种方式有什么关系?为什么实现线程的两种方式中继承Thread类的那个类必须要重写run()方法?为什么用start()方法来启动线程?基于这些问题我们必须去研究线程Thread类的源代码,这样才能只其然且知其所以然,这一讲我们就来讲解Thread类源代码。
1.查看Thread类的源代码可以发现它也实现了Runnable接口
public
class Thread implements Runnable
1) 因为Runnable接口有一个run()方法,所以Thread类实现了run()方法
查看Thread类的关键代码:Thread类的构造方法:public Thread()和 public Thread(Runnable target)
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
比较这两个构造方法可以发现,不带参数的构造方法其实是接受一个Runnable类型的 target 参数为空。
继续跟踪nextThreadNum()方法
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
返回threadInitNumber++,threadInitNumber是一个什么东西呢?
写一个段代码,在上一讲中程序ThreadTest2的main方法中打印出两个线程的名字,用getName()方法
Thread t1 = new Thread(new MyThread());
System.out.println(t1.getName());
t1.start();
Thread t2 = new Thread(new MyThread2());
System.out.println(t2.getName());
t2.start();
输出:Thread-0
Thread-1
继续Thread类的跟踪getName()方法
public final String getName() {
return String.valueOf(name);
}
private charname[];
name是一个char类型的数组
private void init(ThreadGroup g, Runnable target, String name,
long stackSize)
this.name = name.toCharArray();
name是init()方法中被赋值的
所以,在Thread()的构造方法中 "Thread-" + nextThreadNum() 这个参数会赋给name,由于nextThreadNum()是一个静态的成员变量,所以会被所有的线程所共享,所以会输出Thrad-0,从0开始标记一个线程的名称。
2. 查看Thread类的其他构造方法:
1). public Thread(String name)
Allocates a new Thread object. This constructor has the same effect as Thread(null, null, name).
[分配一个新的线程对象,name是新线程的名字]
写一个这种构造方法的Demo
package com.ahuier.thread;
public class ThreadTest {
public static void main(String[] args) {
Thread1 t1 = new Thread1("first Thread");
Thread2 t2 = new Thread2("second Thread");
System.out.println(t1.getName());
System.out.println(t2.getName());
t1.start();
t2.start();
}
}
class Thread1 extends Thread{
public Thread1(String name){
super(name);
}
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("hello world:" + i);
}
}
}
class Thread2 extends Thread{
public Thread2(String name){
super(name);
}
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("welcome" + i);
}
}
}
编译执行结果不全贴出来了:
first Thread
second Thread
welcome0
welcome1
...
3. 为什么要用start()方法来启动线程,查看他们的源代码
查看Thread类的start()方法:
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();
group.add(this);
start0();
if (stopBeforeStart) {
stop0(throwableFromStop);
}
}
private native void start0();直接查看start0()方法是一个本地的方法: private native void start0();看不到start()方法调用run()方法了,所以它是交给底层C语言去实现了,底层CPU帮我们启动一个线程,并且帮我们分配线程资源,
所以线程一旦启动我们是没有办法将其关闭等操纵它的。
4. 查看Thread类的run()
public void run() {
if (target != null) {
target.run();
}
}
private Runnable target;target是Runnable对象,它是通过Thread的构造方法给其赋值的,
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}查看init方法中target的操纵
this.target = target;所以可以确定它是target是通过 new Thread(Runnable target) 的方式传递过来的。
当我们使用第一种生成线程对象方式的时候,它里面所维护的Runnable对象是为空的,此时run()中的if()语句是不执行的,所以想要启动线程我们必须重写run()方法,所以当我们使用第二种生成线程对象的方式的时候,它里面所维护的就是我们传过去的Runnable对象。此时if语句执行,调用了run()方法。
5. 总结
1) Thread类也实现了Runnable接口,因此实现了Runnable接口中的run方法;
2) 当生成一个线程对象时,如果没有为其设定名字,那么线程对象的名字将使用如下形式:Thread-number,该number将是自动增加的,并被所有的Thread对象所共享(因为它是static的成员变量)。
3) 当使用第一种方式来生成线程对象时,我们需要重写run方法,因为Thread类的run方法此时什么事情也不做。
4) 当使用第二种方式来生成线程对象时,我们需要实现Runnable接口的run方法,然后使用new Thread(new MyThread())(假如MyThread已经实现了Runnable接口)来生成线程对象,这时的线程对象的run方法就会调用MyThread类的run方法,这样我们自己编写的run方法就执行了。
6. 掌握了以上的知识,现在我们接下来就来研究一下线程中的一些特性,线程的特性主要是由于线程的不确定性引起的:一旦线程启动,则它能操纵和停止它
未完待续,持续更新中......