创建线程的方式有三种,此处不再赘述,在另一篇文章中已经写过,此处从源码的角度来分析通过继承Thread和通过实现Runnable来创建线程的过程
1.调用thread.start()和thread.run()的区别
Thread.java类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法,就是让系统安排一个时间来调用Thread中的run()方法,也就是使得线程得到运行,启动线程,具有异步的执行效果。
如果调用thread.run()方法,就不是异步了,而是同步,那么此线程对象并不交给线程规划器来进行处理,而是由调用该方法的线程来调用run()方法
另外再多提一点:当开启多个线程时,执行start()方法的顺序并不代表线程启动的顺序,只是将这些线程置于就绪状态,一同等待CPU的时间片来临,然后根据具体的CPU调度算法选择这些线程中的一个执行,其他的继续等待。
public class Test {
public static void main(String[] args){
Mythread myth = new Mythread();
Mythread myth1 = new Mythread();
System.out.println("main thread="+Thread.currentThread().getName());
myth.start();
myth1.start();
myth.run();
}
}
class Mythread extends Thread {
@Override
public void run() {
System.out.println("run="+Thread.currentThread().getName());
}
}
执行结果:
main thread=main
run=main
run=Thread-0
run=Thread-1
从执行结果可知,myth.run()方法并没有另自开辟一个线程,而是直接利用main主线程去同步调用run()方法。而myth.start()以及myth1.start()方法均开辟了新的线程去执行run()方法。
2.通过继承Thread来创建线程
public class MythreadA extends Thread {
@Override
public void run() {
System.out.println("run="+Thread.currentThread().getName());
}
}
public class Test {
public static void main(String[] args) {
MythreadA mythread = new MythreadA();
mythread.start();
System.out.println("main="+Thread.currentThread().getName());
}
}
==> new MythreadA();创建线程对象,MythreadA的无参构造器中会调用父类构造方法,然后做相关的初始化工作。
==> 主线程调用mythread.start();
==> start()方法将mythread添加到线程组,然后调用native方法 start0() (该方法的实现由非java语言实现,比如C。)
==> 线程mythread进入就绪等待CPU调度
==> CPU给mythread分配时间片
==> start0()调用mythread.run()
主线程main和线程mythread实现了并发。
JDK中Thread的部分源码如下:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public synchronized void start() {
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) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
3.通过实现Runnable接口来创建线程
public class TestB {
public static void main(String[] args){
Runnable mythb = new MythreadB();
Thread thread = new Thread(mythb);
thread.start();
}
}
class MythreadB implements Runnable {
@Override
public void run() {
System.out.println("run="+Thread.currentThread().getName());
}
}
==> Runnable mythb = new MythreadB();
==> Thread thread = new Thread(mythb);
==> thread.start();
==> thread.start0();–>等待CPU
==> thread.run()
==> mythb.run()
主线程main和thread实现并发
Thread.java中有构造方法 Thread(Runnable target)
调用此构造方法后,会对target做一些初始化工作,包括线程名称、线程组等的分配。其中会执行一条代码 this.target = target;将Thread的target成员变量赋值为传入的Runnable类型的参数target。
执行thread.run()方法时会先检查当前Thread的target是否为null,如果不为空,则执行target.start()(此处不同于使用继承的方式,由于继承时子类MythreadA重写了Thread的 run()方法,所以线程得到CPU时间片后native方法 start0()直接调用重写后的MythreadA的run()方法,由于此处并没有重写thread的run()方法,所以将使用Thread.java中的run()方法)
JDK部分源码
/* What will be run. */
private Runnable target;
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
init(g, target, name, stackSize, null);
}
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
......(代码过长只截取了其中几个片段,详细代码可自行查看JDK中的源码)
Thread parent = currentThread();
......
this.target = target;
......
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
4.两种方式在并发上的具体细节
在上面的条目2中,主线程main和创建的线程(MythreadA)thread并发执行,输出方法System.out.println(“run=”+Thread.currentThread().getName()); 在MythreadA中。MythreadA extends Thread
条目3中,主线程main和创建的线程(Thread)thread并发执行,输出方法System.out.println(“run=”+Thread.currentThread().getName()); 在MythreadB中。MythreadB implements Runnable
总结:通过继承Thread的线程(MythreadA)是直接与main线程并发执行,而通过实现Runnable方法的线程(MythreadB)是靠新建一个Thread对象,用该对象调用MythreadB的run()方法从而使得MythreadB与主线程main并发执行,然而,新创建的Thread与MythreadB却是同步执行的(在Thread.run()中调用的是target.run(),条目1中已经证明两者属于同步调用),新建的Thread起一个辅助作用,它的run()方法除了调用target的run()之外没有什么其他的操作。
5.如何将一个Thread对象中的run()方法交由其他的线程进行调用
此处用一个简单的例子对条目4中最后的粗体部分进行说明
有三个线程类Parent,child1,child2,
package Chapter1;
public class Parent extends Thread {
public Parent(Runnable target) {
super(target);
}
@Override
public void run() {
super.run();
System.out.println("Parent run="+Thread.currentThread().getName());
}
public static void main(String[] args){
System.out.println("main="+Thread.currentThread().getName());
Parent mother = new Parent(new child2());
Parent father = new Parent(new child1());
mother.start();
father.start();
}
}
class child1 extends Thread{
@Override
public void run() {
System.out.println("child1 run="+Thread.currentThread().getName());
}
}
class child2 implements Runnable{
@Override
public void run() {
System.out.println("child2 run="+Thread.currentThread().getName());
}
}
输出结果:
main=main
child2 run=Thread-0
child1 run=Thread-2
Parent run=Thread-0
Parent run=Thread-2
注意:在Parent的run()方法中显式的调用了父类的run()方法从而调用传入参数target的 target.run()。
由输出结果可知一个存在三个线程:main、thread-0、thread-2
Parent run=Thread-0 <===> child2 run=Thread-0 同步
Parent run=Thread-2 <===> child1 run=Thread-2 同步
根据这个例子,进一步说明了条目4的最后一段话的具体过程。