Thread与Runnable是如何实现多线程的?

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对象。

有问题欢迎提出一起交流

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值