Thread与Runnable是如何实现多线程的?
本文针对论坛中关于Thread与Runnable实现多线程解释存在分歧甚至错误而进行的源码分析总结,希望能帮助到大家。有问题欢迎大家及时提出来一起交流;
当问起java实现多线程的两种主要方式时,我们通常会提到使用Runnable接口与继承Thread类来实现多线程;但两者究竟是如何实现多线程呢?
在我们查看各类博文时,为什么有人说说使用Runnable接口实现多线程会进行资源共享,而使用继承Thread类来实现多线程时资源相对独立?又会有人说使用Thread也可以实现共享资源?带着这些问题,我们从源码上来一起分析一下:(本文以Android源码进行分析)
首先,我们看一下Runnable的源码定义;
java/lang/Runnable.java
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
从源码上可以看出Runnable接口中除了定义一个抽象方法run()以外, 什么也没做。所以《Java编程思想》的作者才会说到“Runnable仅仅定义任务,其自身并不涉及多线程。当其与Thread类绑定后,才有了多线程的意义。”这也是我们在使用Runnable接口实现多线程时,必须使用 new Thread(new Runnbale()) 对Runnable对象进行绑定的原因。
既然Runnable接口不是实现线程的方式,那一定是Thread类了,那Thread类又是如何实现开启新线程工作的呢?
我们也是先看一下Threa类的源码(内容较多,只摘取主要部分):
java/lang/Thread.java
public class Thread implements Runnable {
/* What will be run. */
private Runnable target;
……
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
……
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
……
this.target = target;
}
从Thread类的声明中我们可以看出Thread本身实现了Runnable接口,因此Thread对象也可以看成是一种Runnable对象;
下面是摘取的两种构造方法,也是我们常见的两种创建Thread对象的方法。使用 new Thread() 与使用 new Thread(new Runnable())的区别是调用init()方法时,传入的target参数值不同(一个为null,一个为Runnable对象);而从init方法的定义中,我们看到它会将所传入的target参数赋值到Thread对象的私有属性target中;
因此使用new Thread() 与使用 new Thread(new Runnable())创建的Thread对象的唯一区别是所创建的Thread对象的target属性值不同(一个为null, 一个为Runnable对象);
在init()方法的具体实现中我们发现都是一些变量值类的操作,没有看到与开启新线程有关。这也说明,在Thread对象刚创建时,还没有涉及多线程,此时的它就是一个普通对象。那究竟是如何实现开启新线程呢?
我们在实现多线程时,通常需要调用start()方法,会不会是在这里开启新线程的呢?我们看一下start()方法实现:
public synchronized void start() {
……
try {
// Android-changed: Use Android specific nativeCreate() method to create/start thread.
// start0();
nativeCreate(this, stackSize, daemon);
started = true;
} finally {
……
}
}
private native static void nativeCreate(Thread t, long stackSize, boolean daemon);
果然,里面有一个nativeCreate(this, stackSize, daemon)方法;从该方法的声明可以看出其为一个native方法;其具体实现为:
native/java_lang_Thread.cc
static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size,
jboolean daemon) {
……
Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE);
}
可见其内部调用 CreateNativeThread()方法,具体定义为:
art/runtime/thread.cc
void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
……
pthread_create_result = pthread_create(&new_pthread,
&attr,
Thread::CreateCallback,
child_thread);
……
可见,最终调用的是Linux内核调用 pthread_create()方法来创建多线程;因此Thread对象真正实现多线程是在其调用start()方法后;
明白线程的实现方式后,我们继续回到刚开始的问题,为什么用Runnable接口实现多线程会出现资源共享,而使用Thread来实现,资源就相对独立? 我们知道线程开启后,会执行线程定义的run()方法,该方法也是Runnable接口定义的抽象方法,Thread实现了Runnable接口,那我们看一下其是如何实现该方法的:
java/lang/Thread.java
@Override
public void run() {
if (target != null) {
target.run();
}
}
可见当taget属性不为null时,其调用的为target的run方法,当target属性为null时,才使用Thread对象自身的run方法;而target属性,我们在前文中分析了,当使用Runnable对象创建Thread对象时,target将指向该Runnable对象;
所以,使用实现Runnable接口实现多线程方法时,如:
public class TestRunnable implements Runnable {
private int count = 6;
public void run() {
while(count > 0) {
count--;
System.out.println(Thread.currentThread().getName() + ": count " + count);
}
}
public static void main(String[] args) {
TestRunnable testR = new TestRunnable();
new Thread(testR, "thread1").start();
new Thread(testR, "thread2").start();
}
}
//输出:
thread2: count 4
thread1: count 5
thread2: count 3
thread1: count 2
thread2: count 1
thread1: count 0
可见当使用同一个Runnable对象(testR)来创建线程对象(thread1,thread2)时,他们将共用一个Runnable对象的资源;且由前面分析可知,线程此时执行的run方法为Runnable对象的run方法,则使用的count来自其共同使用的Runnable对象。
当使用继承Thread类来实现多线程方法时,如:
public class TestThread extends Thread {
private int count = 6;
public TestThread() {
super();
}
public TestThread(String name) {
super(name);
}
public void run() {
while(count > 0) {
count--;
System.out.println(Thread.currentThread().getName() + ": count " + count);
}
}
public static void main(String[] args) {
new TestThread("thread3").start();
new TestThread("thread4").start();
//TestThread testThread = new TestThread();
//new Thread(testThread, "thread5").start();
//new Thread(testThread, "thread6").start();
}
}
//输出
thread4: count 5
thread3: count 5
thread4: count 4
thread4: count 3
thread4: count 2
thread4: count 1
thread4: count 0
thread3: count 4
thread3: count 3
thread3: count 2
thread3: count 1
thread3: count 0
可见使用继承Thread类来实现多线程时,其由于重写了父类Thread的run方法,所以将以重定义的run方法执行;且此时run方法中的count为Thread对象自身的成员变量,彼此相互独立(类变量就会出现竞争了);
有人说,使用 new Thread()创建对象,再用该对象创建新线程也可以实现资源共享,如代码中的thread5,6,我们暂且不论其合规性,其采用的与使用Runnable对象实现多线程的方式是相同的,因为Thread对象本身也是Runnable对象;所以仍然相当于使用同一个Runnable对象来创建不同的Thread对象。
有问题欢迎提出一起交流