问题背景
在日常安卓开发和学习过程中,我们很可能习惯性地选择Runnable或Thread之一直接使用,那么问题来了,Runnable和Thread的区别是啥?一般来说这二者就是接口和类的区别。比如: (1)Runnable的实现方式是实现其接口即可 (2)Thread的实现方式是继承其类 (3)Runnable接口支持多继承,但基本上用不到 (4)Thread实现了Runnable接口并进行了扩展,而Thread和Runnable的实质是实现的关系,不是同类东西。 我们一起看看网络上流传的结论:Runnable支持多线程间的资源共享,而Thread不可以!我们一起来看看这个结论是怎么得出的呢?
问题分析
首先我们来稍微梳理下Thread相关的部分源码。 1、java.lang.Thread#Thread()
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
2、java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)
/**
* Initializes a Thread with the current AccessControlContext.
* @see #init(ThreadGroup,Runnable,String,long,AccessControlContext)
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
3、java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext)
/**
* Initializes a Thread.
* ...
*/
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;
Thread parent = currentThread();
if (g == null) {
g = parent.getThreadGroup();
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
// 传进来的runnable对象
this.target = target;
init2(parent);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
4、java.lang.Thread#init2
private void init2(Thread parent) {
this.contextClassLoader = parent.getContextClassLoader();
this.inheritedAccessControlContext = AccessController.getContext();
if (parent.inheritableThreadLocals != null) {
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(
parent.inheritableThreadLocals);
}
}
5、java.lang.ThreadLocal#createInheritedMap
/**
* Factory method to create map of inherited thread locals.
* Designed to be called only from Thread constructor.
*
* @param parentMap the map associated with parent thread
* @return a map containing the parent's inheritable bindings
*/
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
6、java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMap(java.lang.ThreadLocal.ThreadLocalMap)
/**
* Construct a new map including all Inheritable ThreadLocals
* from given parent map. Called only by createInheritedMap.
*
* @param parentMap the map associated with parent thread.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
// 通过entry数组,也是构造了 <ThreadLocal, value>的映射
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
7、java.lang.Thread#start
public synchronized void start() {
if (started)
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);
started = false;
try {
nativeCreate(this, stackSize, daemon);
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 */
}
}
}
8、java.lang.Thread#run
@Override
public void run() {
// 传进来的runnable对象
if (target != null) {
target.run();
}
}
问题解决
现在我们具体来看看网上流传结论得出的有关代码 (1)使用Thread来卖票,三个线程,每个线程都各卖了5张票,代码如下:
fun main() {
testThread()
}
fun testThread() {
val thread1 = MyThread()
val thread2 = MyThread()
val thread3 = MyThread()
thread1.start()
thread2.start()
thread3.start()
}
class MyThread : Thread() {
private var ticket = 5
override fun run() {
while(true){
if(ticket <= 0){
break;
}
println(currentThread().toString() + "Runnable ticket = " + ticket--)
}
}
}
运行结果如下: 运行结果分析: 每个线程卖了5张,这样一共就卖了15张票了。 (2)使用Runnable来卖票,三个线程,每个线程都各卖了5张票,代码如下:
fun main() {
testRunnable()
}
fun testRunnable() {
val mt = MyRunnable()
val thread1 = Thread(mt)
val thread2 = Thread(mt)
val thread3 = Thread(mt)
thread1.start()
thread2.start()
thread3.start()
thread1.join()
thread2.join()
thread3.join()
println(MyRunnable.count)
}
class MyRunnable: Runnable{
var ticket = 5
companion object {
var count = 0
}
override fun run() {
while(true){
if(ticket <= 0){
break;
}
println(Thread.currentThread().toString() + "Runnable ticket = " + ticket--)
count++
}
}
}
运行结果如下: 运行结果分析: 三个线程一共卖了5张票。 结果对比分析: 根据上面两个demo的对比呢,网上就流传一个结论,Runnable比Thread的优势,支持多线程间的资源共享? 其实通过刚才Thread类部分源码的梳理,我们可以看到,调用Thread类的start方法后,线程进入相应的就绪状态,等待线程获取cpu时间片之后呢开始运行,调用run()方法,thread的方式实现是继承Runable类,那么demo中三个线程都是分别执行自己重写的run(),每个线程单独内部都有各自的Runnable对象;然后如果是通过传入Runnable的方式,其实三个线程内部的runnable对象都是指向同一个runnable,所以看起来是共享资源的效果,但是所谓的资源共享也可能存在并发上的一些问题,如果要使用这种所谓的共享,需要注意相关同步操作。比如以下demo,就出现并发问题了,代码如下:
fun main() {
testRunnable()
}
fun testRunnable() {
val mt = MyRunnable()
val thread1 = Thread(mt)
val thread2 = Thread(mt)
val thread3 = Thread(mt)
thread1.start()
thread2.start()
thread3.start()
thread1.join()
thread2.join()
thread3.join()
println(MyRunnable.count)
}
class MyRunnable: Runnable{
var ticket = 10000
companion object {
var count = 0
}
override fun run() {
while(true){
if(ticket <= 0){
break;
}
println(Thread.currentThread().toString() + "Runnable ticket = " + ticket--)
count++
}
}
}
运行结果如下:
问题总结
本文稍微梳理了Runnable和Thread的区别,并对网上流传的“Runnable比Thread的优势,支持多线程间的资源共享”进行了说明和思考,有兴趣的同学可以进一步深入研究。